From 14ac234daf1680b0685f6bea3e74aa09dfcf38ef Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 14 Dec 2013 13:23:13 +0100 Subject: - plugin list now highlights plugins with attached ini files - bugfix: opening nexus through the globe icon used the nmm.nexusmods.com url --- src/resources.qrc | 1 + 1 file changed, 1 insertion(+) (limited to 'src/resources.qrc') diff --git a/src/resources.qrc b/src/resources.qrc index 3f27e2cc..1582a3f2 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -51,5 +51,6 @@ resources/accessories-text-editor.png resources/x-office-calendar.png resources/dialog-warning_16.png + resources/mail-attachment.png -- cgit v1.3.1 From e597823337c858f2985c615ee5147f47567991ee Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 23 Jan 2014 00:05:46 +0100 Subject: - boss integration - plugin list can now also display multiple flags for a file (like the mod list) - changed some compiler&linker settings to produce smaller binaries --- src/ModOrganizer.pro | 3 +- src/aboutdialog.cpp | 2 + src/icondelegate.cpp | 30 +-- src/icondelegate.h | 5 +- src/lockeddialog.cpp | 104 ++++++----- src/lockeddialog.h | 96 +++++----- src/logbuffer.cpp | 3 +- src/main.cpp | 5 +- src/mainwindow.cpp | 26 ++- src/mainwindow.h | 1 + src/mainwindow.ui | 82 +++++++-- src/modflagicondelegate.cpp | 50 +++++ src/modflagicondelegate.h | 18 ++ src/organizer.pro | 32 ++-- src/pdll.h | 402 +++++++++++++++++++++++++++++++++++++++++ src/pluginflagicondelegate.cpp | 24 +++ src/pluginflagicondelegate.h | 17 ++ src/pluginlist.cpp | 270 +++++++++++++++++++++++---- src/pluginlist.h | 95 +++++++++- src/pluginlistsortproxy.cpp | 5 - src/profile.cpp | 34 +--- src/resources.qrc | 2 +- src/safewritefile.cpp | 48 +++++ src/safewritefile.h | 51 ++++++ 24 files changed, 1179 insertions(+), 226 deletions(-) create mode 100644 src/modflagicondelegate.cpp create mode 100644 src/modflagicondelegate.h create mode 100644 src/pdll.h create mode 100644 src/pluginflagicondelegate.cpp create mode 100644 src/pluginflagicondelegate.h create mode 100644 src/safewritefile.cpp create mode 100644 src/safewritefile.h (limited to 'src/resources.qrc') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index dbf6ab6e..05b05855 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,11 +13,12 @@ SUBDIRS = bsatk \ nxmhandler \ BossDummy \ pythonRunner \ + boss_modified \ esptk plugins.depends = pythonRunner hookdll.depends = shared -organizer.depends = shared uibase plugins +organizer.depends = shared uibase plugins boss_modified CONFIG(debug, debug|release) { DESTDIR = outputd diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index c7f95640..a8f9b4db 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -44,6 +44,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("7-zip", LICENSE_LGPL3); addLicense("ZLib", LICENSE_ZLIB); addLicense("NIF File Format Library", LICENSE_BSD3); + addLicense("BOSS (modified)", LICENSE_GPL3); + addLicense("Alphanum Algorithm", LICENSE_ZLIB); ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); #ifdef HGID diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 2540e1d5..048b09f9 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -29,38 +29,17 @@ IconDelegate::IconDelegate(QObject *parent) } -QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); - case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); - case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); - case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); - default: return QIcon(); - } -} - - void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); - QVariant modid = index.data(Qt::UserRole + 1); - if (!modid.isValid()) { - return; - } - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - std::vector flags = info->getFlags(); + + QList icons = getIcons(index); int x = 4; painter->save(); painter->translate(option.rect.topLeft()); - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - QIcon temp = getFlagIcon(*iter); - painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16))); + for (auto iter = icons.begin(); iter != icons.end(); ++iter) { + painter->drawPixmap(x, 2, 16, 16, iter->pixmap(QSize(16, 16))); x += 20; } @@ -70,6 +49,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const { + int count = getNumIcons(modelIndex); unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); if (index < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(index); diff --git a/src/icondelegate.h b/src/icondelegate.h index dd9f9dfc..cf292443 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -34,13 +34,16 @@ public: virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + signals: public slots: private: - QIcon getFlagIcon(ModInfo::EFlag flag) const; + virtual QList getIcons(const QModelIndex &index) const = 0; + virtual size_t getNumIcons(const QModelIndex &index) const = 0; + private: diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 4c19c615..065d9270 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -17,51 +17,59 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "lockeddialog.h" -#include "ui_lockeddialog.h" -#include - - -LockedDialog::LockedDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::LockedDialog), m_UnlockClicked(false) -{ - ui->setupUi(this); - - this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); - - if (parent != NULL) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != NULL) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialog::on_unlockButton_clicked() -{ - m_UnlockClicked = true; -} +#include "lockeddialog.h" +#include "ui_lockeddialog.h" +#include + + +LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) + : QDialog(parent) + , ui(new Ui::LockedDialog) + , m_UnlockClicked(false) +{ + ui->setupUi(this); + + this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); + + if (parent != NULL) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } + + if (text.length() > 0) { + ui->label->setText(text); + } + if (!unlockButton) { + ui->unlockButton->hide(); + } +} + +LockedDialog::~LockedDialog() +{ + delete ui; +} + + +void LockedDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + + +void LockedDialog::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != NULL) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialog::on_unlockButton_clicked() +{ + m_UnlockClicked = true; +} diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 5be398bc..0f3b29b0 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,51 +17,51 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H - -#include - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public QDialog -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0); - ~LockedDialog(); - - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockClicked() const { return m_UnlockClicked; } - - void setProcessName(const QString &name); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private slots: - - void on_unlockButton_clicked(); - -private: - Ui::LockedDialog *ui; - bool m_UnlockClicked; -}; - -#endif // LOCKEDDIALOG_H +#ifndef LOCKEDDIALOG_H +#define LOCKEDDIALOG_H + +#include + +namespace Ui { + class LockedDialog; +} + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); + ~LockedDialog(); + + /** + * @brief see if the user clicked the unlock-button + * + * @return true if the user clicked the unlock button + **/ + bool unlockClicked() const { return m_UnlockClicked; } + + void setProcessName(const QString &name); + +protected: + + virtual void resizeEvent(QResizeEvent *event); + +private slots: + + void on_unlockButton_clicked(); + +private: + Ui::LockedDialog *ui; + bool m_UnlockClicked; +}; + +#endif // LOCKEDDIALOG_H diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index 4f72e551..689d2b55 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "report.h" #include #include +#include #include QScopedPointer LogBuffer::s_Instance; @@ -124,8 +125,6 @@ char LogBuffer::msgTypeID(QtMsgType type) } } -#include - void LogBuffer::log(QtMsgType type, const char *message) { QMutexLocker guard(&s_Mutex); diff --git a/src/main.cpp b/src/main.cpp index 8b74ba83..05d68ec7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -119,7 +119,8 @@ bool bootstrap() QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss") }; static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); @@ -284,7 +285,7 @@ int main(int argc, char *argv[]) SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(20, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); + LogBuffer::init(200, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 582bc283..0f98989c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -48,7 +48,8 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "questionboxmemory.h" #include "tutorialmanager.h" -#include "icondelegate.h" +#include "modflagicondelegate.h" +#include "pluginflagicondelegate.h" #include "credentialsdialog.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -104,6 +105,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS @@ -186,7 +188,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->setModel(m_ModListSortProxy); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(ui->modList)); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); //ui->modList->setAcceptDrops(true); ui->modList->header()->installEventFilter(&m_ModList); if (initSettings.contains("mod_list_state")) { @@ -206,6 +208,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->espList->setModel(m_PluginListSortProxy); ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); if (initSettings.contains("plugin_list_state")) { ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); } @@ -1314,6 +1317,8 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg QCoreApplication::processEvents(); } + m_CurrentProfile->writeModlistNow(true); + // TODO: should also pass arguments if (m_AboutToRun(binary.absoluteFilePath())) { return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); @@ -1778,7 +1783,7 @@ void MainWindow::refreshESPList() m_CurrentProfile->getLoadOrderFileName(), m_CurrentProfile->getLockedOrderFileName()); } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %s").arg(e.what())); + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } } @@ -5123,3 +5128,18 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } +void MainWindow::on_bossButton_clicked() +{ + try { + LockedDialog dialog(this, tr("BOSS working"), false); + dialog.show(); + qApp->processEvents(); + m_PluginList.bossSort(); + + savePluginList(); + dialog.hide(); + } catch (const std::exception &e) { + reportError(tr("failed to run boss: %1").arg(e.what())); + ui->bossButton->setEnabled(false); + } +} diff --git a/src/mainwindow.h b/src/mainwindow.h index c01ce767..b247b924 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -564,6 +564,7 @@ private slots: // ui slots void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5c89a7e0..f3bb425c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -31,7 +31,16 @@ 4 - + + 6 + + + 6 + + + 6 + + 6 @@ -705,14 +714,25 @@ p, li { white-space: pre-wrap; } - - - - - - Namefilter - - + + + + + + + + Namefilter + + + + + + + Sort + + + + @@ -721,7 +741,16 @@ p, li { white-space: pre-wrap; } Archives - + + 6 + + + 6 + + + 6 + + 6 @@ -801,7 +830,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Data - + + 6 + + + 6 + + + 6 + + 6 @@ -871,7 +909,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Saves - + + 6 + + + 6 + + + 6 + + 6 @@ -900,7 +947,16 @@ p, li { white-space: pre-wrap; } Downloads - + + 2 + + + 2 + + + 2 + + 2 diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp new file mode 100644 index 00000000..db69eb52 --- /dev/null +++ b/src/modflagicondelegate.cpp @@ -0,0 +1,50 @@ +#include "modflagicondelegate.h" +#include + + +ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent) + : IconDelegate(parent) +{ +} + +QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + QVariant modid = index.data(Qt::UserRole + 1); + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + std::vector flags = info->getFlags(); + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + result.append(getFlagIcon(*iter)); + } + } + return result; +} + +QIcon ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); + case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); + default: return QIcon(); + } +} + +size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const +{ + unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + return info->getFlags().size(); + } else { + return 0; + } +} + diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h new file mode 100644 index 00000000..800e2741 --- /dev/null +++ b/src/modflagicondelegate.h @@ -0,0 +1,18 @@ +#ifndef MODFLAGICONDELEGATE_H +#define MODFLAGICONDELEGATE_H + +#include "icondelegate.h" + +class ModFlagIconDelegate : public IconDelegate +{ +public: + explicit ModFlagIconDelegate(QObject *parent = 0); +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; + + QIcon getFlagIcon(ModInfo::EFlag flag) const; + +}; + +#endif // MODFLAGICONDELEGATE_H diff --git a/src/organizer.pro b/src/organizer.pro index fd72dea3..9a9d0da3 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -81,7 +81,10 @@ SOURCES += \ previewgenerator.cpp \ previewdialog.cpp \ aboutdialog.cpp \ - json.cpp + json.cpp \ + safewritefile.cpp \ + modflagicondelegate.cpp \ + pluginflagicondelegate.cpp HEADERS += \ @@ -152,7 +155,11 @@ HEADERS += \ previewgenerator.h \ previewdialog.h \ aboutdialog.h \ - json.h + json.h \ + safewritefile.h\ + pdll.h \ + modflagicondelegate.h \ + pluginflagicondelegate.h FORMS += \ transfersavesdialog.ui \ @@ -185,24 +192,28 @@ FORMS += \ previewdialog.ui \ aboutdialog.ui -INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk "$(BOOSTPATH)" +INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$(BOOSTPATH)" LIBS += -L"$(BOOSTPATH)/stage/lib" CONFIG(debug, debug|release) { OUTDIR = $$OUT_PWD/debug DSTDIR = $$PWD/../../outputd - LIBS += -L$$OUT_PWD/../shared/debug -L$$OUT_PWD/../bsatk/debug - LIBS += -L$$OUT_PWD/../uibase/debug - LIBS += -lDbgHelp + LIBS += -L$$OUT_PWD/../shared/debug + LIBS += -L$$OUT_PWD/../bsatk/debug + LIBS += -L$$OUT_PWD/../uibase/debug + LIBS += -L$$OUT_PWD/../boss_modified/debug + LIBS += -lDbgHelp } else { OUTDIR = $$OUT_PWD/release DSTDIR = $$PWD/../../output - LIBS += -L$$OUT_PWD/../shared/release -L$$OUT_PWD/../bsatk/release + LIBS += -L$$OUT_PWD/../shared/release + LIBS += -L$$OUT_PWD/../bsatk/release LIBS += -L$$OUT_PWD/../uibase/release - QMAKE_CXXFLAGS += /Zi + LIBS += -L$$OUT_PWD/../boss_modified/release + QMAKE_CXXFLAGS += /Zi /GL # QMAKE_CXXFLAGS -= -O2 - QMAKE_LFLAGS += /DEBUG + QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF } #QMAKE_CXXFLAGS_WARN_ON -= -W3 @@ -299,9 +310,6 @@ OTHER_FILES += \ tutorials/tutorial_window_installer.js \ tutorials/tutorials_installdialog.qml -INCLUDEPATH += "$(ZLIBPATH)" "$(ZLIBPATH)/build" "$(BOOSTPATH)" -LIBS += -L"$(ZLIBPATH)/build" -lzlibstatic - # leak detection with vld #INCLUDEPATH += "E:/Visual Leak Detector/include" diff --git a/src/pdll.h b/src/pdll.h new file mode 100644 index 00000000..6e1d06f3 --- /dev/null +++ b/src/pdll.h @@ -0,0 +1,402 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Class: PDll // +// Authors: MicHael Galkovsky // +// Date: April 14, 1998 // +// Company: Pervasive Software // +// Purpose: Base class to wrap dynamic use of dll // +////////////////////////////////////////////////////////////////////////////////////////////// + +#if !defined (_PDLL_H_) +#define _PDLL_H_ + +#include +#include + +#include +#include +#include + + +#define FUNC_LOADED 3456 + +//function declarations according to the number of parameters +//define the type +//declare a variable of that type +//declare a member function by the same name as the dll function +//check for dll handle +//if this is the first call to the function then try to load it +//if not then if the function was loaded successfully make a call to it +//otherwise return a NULL cast to the return parameter. + +#define DECLARE_FUNCTION0(CallType, retVal, FuncName) \ + typedef retVal (CallType* TYPE_##FuncName)(); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName() \ + { \ + if (m_dllHandle) \ + { \ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(); \ + else \ + return (retVal)NULL; \ + } \ + else \ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION1(CallType,retVal, FuncName, Param1) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName(Param1 p1) \ + { \ + if (m_dllHandle) \ + { \ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1); \ + else \ + return (retVal)NULL; \ + } \ + else \ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION2(CallType,retVal, FuncName, Param1, Param2) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2); \ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION3(CallType,retVal, FuncName, Param1, Param2, Param3) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED; \ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION4(CallType,retVal, FuncName, Param1, Param2, Param3, Param4) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION5(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION6(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION7(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION8(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8);\ + else \ + return (retVal)NULL; \ + }\ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION9(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_NAME != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + else \ + return (retVal)NULL; \ + }\ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION10(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10) \ + typedef retVal (CallType* TYPE_##FuncName)FuncName(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9, Param10 p10) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);\ + else \ + return (retVal)NULL; \ + }\ + else \ + return (retVal)NULL;\ + } + +//declare constructors and LoadFunctions +#define DECLARE_CLASS(ClassName) \ + public: \ + ClassName (LPCTSTR name){LoadDll(name);} \ + ClassName () {PDLL();} + +class PDLL +{ +protected: + HINSTANCE m_dllHandle; +private: + LPTSTR m_dllName; + int m_refCount; + +public: + + PDLL() + { + m_dllHandle = NULL; + m_dllName = NULL; + m_refCount = 0; + } + + //A NULL here means the name has already been set + void LoadDll(LPCTSTR name, bool showMsg = true) + { + if (name) + SetDllName(name); + + //try to load + m_dllHandle = LoadLibrary(m_dllName); + + if (m_dllHandle == NULL && showMsg) + { + std::ostringstream message; + message << "failed to load dll: " << ::GetLastError(); + throw std::runtime_error(message.str().c_str()); + } + } + + bool SetDllName(LPCTSTR newName) + { + bool retVal = false; + + //we allow name resets only if the current DLL handle is invalid + //once they've hooked into a DLL, the name cannot be changed + if (!m_dllHandle) + { + if (m_dllName) + { + delete []m_dllName; + m_dllName = NULL; + } + + //They may be setting this null (e.g., uninitialize) + if (newName) + { + m_dllName = new TCHAR[_tcslen(newName) + 1]; + _tcscpy(m_dllName, newName); + } + retVal = true; + } + return retVal; + } + + virtual bool Initialize(short showMsg = 1) + { + + bool retVal = false; + + //Add one to our internal reference counter + m_refCount++; + + if (m_refCount == 1 && m_dllName) //if this is first time, load the DLL + { + //we are assuming the name is already set + LoadDll(NULL, showMsg); + retVal = (m_dllHandle != NULL); + } + return retVal; + } + + virtual void Uninitialize(void) + { + //If we're already completely unintialized, early exit + if (!m_refCount) + return; + //if this is the last time this instance has been unitialized, + //then do a full uninitialization + m_refCount--; + + if (m_refCount < 1) + { + if (m_dllHandle) + { + FreeLibrary(m_dllHandle); + m_dllHandle = NULL; + } + + SetDllName(NULL); //clear out the name & free memory + } + } + + virtual ~PDLL() + { + //force this to be a true uninitialize + m_refCount = 1; + Uninitialize(); + + //free name + if (m_dllName) + { + delete [] m_dllName; + m_dllName = NULL; + } + } + +}; +#endif diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp new file mode 100644 index 00000000..6c0bb29e --- /dev/null +++ b/src/pluginflagicondelegate.cpp @@ -0,0 +1,24 @@ +#include "pluginflagicondelegate.h" +#include "pluginlist.h" +#include + + +PluginFlagIconDelegate::PluginFlagIconDelegate(QObject *parent) + : IconDelegate(parent) +{ +} + +QList PluginFlagIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + foreach (const QVariant &var, index.data(Qt::UserRole + 1).toList()) { + result.append(var.value()); + } + return result; +} + +size_t PluginFlagIconDelegate::getNumIcons(const QModelIndex &index) const +{ + return index.data(Qt::UserRole + 1).toList().count(); +} + diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h new file mode 100644 index 00000000..554e968b --- /dev/null +++ b/src/pluginflagicondelegate.h @@ -0,0 +1,17 @@ +#ifndef PLUGINFLAGICONDELEGATE_H +#define PLUGINFLAGICONDELEGATE_H + +#include "icondelegate.h" + +class PluginFlagIconDelegate : public IconDelegate +{ +public: + PluginFlagIconDelegate(QObject *parent = NULL); + + // IconDelegate interface +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; +}; + +#endif // PLUGINFLAGICONDELEGATE_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 85137390..c2e14182 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -20,8 +20,10 @@ along with Mod Organizer. If not, see . #include "pluginlist.h" #include "report.h" #include "inject.h" -#include #include "settings.h" +#include "safewritefile.h" +#include "scopeguard.h" +#include #include #include #include @@ -39,7 +41,6 @@ along with Mod Organizer. If not, see . #include #include -#include #include #include #include @@ -74,8 +75,10 @@ bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { } PluginList::PluginList(QObject *parent) - : QAbstractTableModel(parent), - m_FontMetrics(QFont()), m_SaveTimer(this) + : QAbstractTableModel(parent) + , m_FontMetrics(QFont()) + , m_SaveTimer(this) + , m_BOSS(NULL) { m_SaveTimer.setSingleShot(true); connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); @@ -92,6 +95,12 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { + if (m_BOSS != NULL) { + m_BOSS->DestroyBossDb(m_BOSSDB); + m_BOSS->CleanUpAPI(); + delete m_BOSS; + m_BOSS = NULL; + } } @@ -101,6 +110,7 @@ QString PluginList::getColumnName(int column) case COL_NAME: return tr("Name"); case COL_PRIORITY: return tr("Priority"); case COL_MODINDEX: return tr("Mod Index"); + case COL_FLAGS: return tr("Flags"); default: return tr("unknown"); } } @@ -205,7 +215,7 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD emit layoutChanged(); refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1)); + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); m_Refreshed(); } @@ -375,18 +385,16 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } + void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; - file.resize(0); + file->resize(0); - file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); + file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); bool invalidFileNames = false; int writtenCount = 0; @@ -398,36 +406,34 @@ void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) cons invalidFileNames = true; qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); } else { - file.write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); + file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); } - file.write("\r\n"); + file->write("\r\n"); ++writtenCount; } } - file.close(); if (invalidFileNames) { reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " "Please see mo_interface.log for a list of affected plugins and rename them.")); } + file.commit(); + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } void PluginList::writeLockedOrder(const QString &fileName) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->resize(0); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) { - file.write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); + file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); } - file.close(); + file.commit(); } @@ -590,6 +596,175 @@ void PluginList::refreshLoadOrder() emit layoutChanged(); } + +class boss_exception : public std::runtime_error { +public: + boss_exception(const std::string &message) : std::runtime_error(message) {} +}; + +#define THROW_BOSS_ERROR(obj) \ + uint8_t *message; \ + obj->GetLastErrorDetails(&message); \ + throw boss_exception(std::string(reinterpret_cast(message))); + +#define U8(text) reinterpret_cast(text) + + +void outputBossLog(const QString &filename) +{ + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + if (line.length() < 1) + continue; + + int endFirstWord = line.indexOf(':'); + if (endFirstWord < 0) { + endFirstWord = 0; + } + QString firstWord = line.mid(0, endFirstWord); + if (firstWord == "DEBUG") { + qDebug("(boss) %s", line.mid(endFirstWord + 2).constData()); + } else { + qWarning("(boss) %s", line.mid(endFirstWord + 2).constData()); + } + } + } + file.resize(0); +} + + +void PluginList::initBoss() +{ + m_BOSS = new BossDLL(TEXT("dlls\\boss.dll")); + + if (!m_BOSS->IsCompatibleVersion(2,1,1)) { + throw MyException(tr("BOSS dll incompatible")); + } + uint8_t *versionString; + if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + qDebug("using boss version %s", versionString); + + m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name + m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4); + + if (m_BOSS->CreateBossDb(&m_BOSSDB, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) { + uint8_t *message; + m_BOSS->GetLastErrorDetails(&message); + std::string messageCopy(reinterpret_cast(message)); + delete m_BOSS; + m_BOSS = NULL; + throw boss_exception(messageCopy); + } + qApp->processEvents(); + + QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt"); + + uint32_t res = m_BOSS->UpdateMasterlist(m_BOSSDB, U8(masterlistName.toUtf8().constData())); + qApp->processEvents(); + if (res == BossDLL::RESULT_OK) { + qDebug("boss masterlist updated"); + } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) { + qDebug("boss masterlist already up-to-date"); + } else { + THROW_BOSS_ERROR(m_BOSS) + } + if (m_BOSS->Load(m_BOSSDB, + U8(masterlistName.toUtf8().constData()), + U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::convertPluginListForBoss(boost::ptr_vector &inputPlugins, std::vector &activePlugins) +{ + foreach (int idx, m_ESPsByPriority) { + QString fileName = m_ESPs[idx].m_Name; + QByteArray name = fileName.toUtf8(); + + uint8_t *nameU8 = new uint8_t[name.length() + 1]; + memcpy(nameU8, name.constData(), name.length() + 1); + if (m_ESPs[idx].m_Enabled) { + activePlugins.push_back(nameU8); + } + inputPlugins.push_back(nameU8); + } + if (m_BOSS->SetActivePluginsDumb(m_BOSSDB, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension) +{ + for (size_t i= 0; i < size; ++i) { + QString name = QString::fromUtf8(reinterpret_cast(pluginList[i])).toLower(); + if (name.endsWith(extension)) { + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + throw MyException("boss returned invalid data"); + } + BossMessage *message; + size_t numMessages = 0; + m_BOSS->GetPluginMessages(m_BOSSDB, pluginList[i], &message, &numMessages); + m_ESPs[iter->second].m_BOSSMessages.clear(); + for (size_t im = 0; im < numMessages; ++im) { + m_ESPs[iter->second].m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast(message[im].message))); + } + m_ESPs[iter->second].m_Priority = priority++; + m_ESPs[iter->second].m_BOSSUnrecognized = !recognized; + } + } +} + +void PluginList::bossSort() +{ + if (m_BOSS == NULL) { + // first run, check boss compatibility and update + initBoss(); + } + + // create a boss-compatible representation of our current mod list. + boost::ptr_vector inputPlugins; + std::vector activePlugins; + convertPluginListForBoss(inputPlugins, activePlugins); + + // sort mods in-memory + uint8_t **sortedPlugins; + uint8_t **unrecognizedPlugins; + size_t sizeSorted, sizeUnrecognized; + + if (m_BOSS->SortCustomMods(m_BOSSDB, + inputPlugins.c_array(), inputPlugins.size(), + &sortedPlugins, &sizeSorted, + &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + + // output the log from boss to our own log to make it visible + outputBossLog(m_TempFile.fileName()); + + qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized); + + emit layoutAboutToBeChanged(); + + int priority = 0; + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esm"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esm"); + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esp"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esp"); + + // inform view of the changed data + updateIndices(); + emit layoutChanged(); + syncLoadOrder(); + + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); + m_Refreshed(); +} + IPluginList::PluginState PluginList::state(const QString &name) const { auto iter = m_ESPsByName.find(name.toLower()); @@ -670,7 +845,7 @@ int PluginList::rowCount(const QModelIndex &parent) const int PluginList::columnCount(const QModelIndex &) const { - return 3; + return COL_LASTCOLUMN + 1; } @@ -724,18 +899,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } break; } - } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_MasterUnset.size() > 0) { - return QIcon(":/MO/gui/warning"); - } else if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { - return QIcon(":/MO/gui/locked"); - } else if (m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/attachment"); - } else if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/edit_clear"); - } else { - return QVariant(); - } } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::FontRole) { @@ -754,8 +917,15 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } } else if (role == Qt::ToolTipRole) { + QString toolTip; + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + toolTip += m_ESPs[index].m_BOSSMessages.join("
") + "

"; + } + if (m_ESPs[index].m_BOSSUnrecognized) { + toolTip += "Not recognized by BOSS
"; + } if (m_ESPs[index].m_ForceEnabled) { - return tr("This plugin can't be disabled (enforced by the game)"); + toolTip += tr("This plugin can't be disabled (enforced by the game)"); } else { QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); if (m_ESPs[index].m_MasterUnset.size() > 0) { @@ -773,8 +943,30 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const text += "
This file is a dummy! It exists only so the bsa with the same name gets loaded. With MO you don't need this: " "If you enable the archive with the same name in the \"Archive\" tab you can disable this plugin."; } - return text; + toolTip += text; + } + return toolTip; + } else if (role == Qt::UserRole + 1) { + QVariantList result; + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(QIcon(":/MO/gui/warning")); + } + if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { + result.append(QIcon(":/MO/gui/locked")); } + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + result.append(QIcon(":/MO/gui/information")); + } + if (m_ESPs[index].m_BOSSUnrecognized) { + result.append(QIcon(":/MO/gui/help")); + } + if (m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/attachment")); + } + if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/edit_clear")); + } + return result; } else { return QVariant(); } @@ -784,7 +976,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { -qDebug("uncheck plugin"); m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; emit dataChanged(modIndex, modIndex); @@ -1036,7 +1227,8 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni) : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), m_Removed(false), - m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) + m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni), + m_BOSSUnrecognized(false) { try { ESP::File file(ToWString(fullPath)); diff --git a/src/pluginlist.h b/src/pluginlist.h index 64c914df..bb7428d0 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -25,8 +25,12 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include +#include #include +#include +#include /** @@ -40,6 +44,7 @@ public: enum EColumn { COL_NAME, + COL_FLAGS, COL_PRIORITY, COL_MODINDEX, @@ -143,6 +148,8 @@ public: void refreshLoadOrder(); + void bossSort(); + public: virtual PluginState state(const QString &name) const; virtual int priority(const QString &name) const; @@ -162,6 +169,7 @@ public: // implementation of the QAbstractTableModel interface virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + void applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension); public slots: /** @@ -194,7 +202,6 @@ private: ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); QString m_Name; - QString m_FullPath; bool m_Enabled; bool m_ForceEnabled; bool m_Removed; @@ -207,12 +214,91 @@ private: bool m_HasIni; std::set m_Masters; mutable std::set m_MasterUnset; + QStringList m_BOSSMessages; + bool m_BOSSUnrecognized; }; friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); + + class BossDLL : public PDLL { + DECLARE_CLASS(BossDLL) + + DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*) + DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db) + DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI) + + DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t) + + DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*) + + DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*) + + DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t) + DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**) + + DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**) + DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t) + + DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*) + + enum ResultCode { + RESULT_OK = 0, + RESULT_NO_MASTER_FILE = 1, + RESULT_FILE_READ_FAIL = 2, + RESULT_FILE_WRITE_FAIL = 3, + RESULT_FILE_NOT_UTF8 = 4, + RESULT_FILE_NOT_FOUND = 5, + RESULT_FILE_PARSE_FAIL = 6, + RESULT_CONDITION_EVAL_FAIL = 7, + RESULT_REGEX_EVAL_FAIL = 8, + RESULT_NO_GAME_DETECTED = 9, + RESULT_ENCODING_CONVERSION_FAIL = 10, + RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11, + RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12, + RESULT_READ_UPDATE_FILE_LIST_FAIL = 13, + RESULT_FILE_CRC_MISMATCH = 14, + RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15, + RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16, + RESULT_FS_FILE_RENAME_FAIL = 17, + RESULT_FS_FILE_DELETE_FAIL = 18, + RESULT_FS_CREATE_DIRECTORY_FAIL = 19, + RESULT_FS_ITER_DIRECTORY_FAIL = 20, + RESULT_CURL_INIT_FAIL = 21, + RESULT_CURL_SET_ERRBUFF_FAIL = 22, + RESULT_CURL_SET_OPTION_FAIL = 23, + RESULT_CURL_SET_PROXY_FAIL = 24, + RESULT_CURL_SET_PROXY_TYPE_FAIL = 25, + RESULT_CURL_SET_PROXY_AUTH_FAIL = 26, + RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27, + RESULT_CURL_PERFORM_FAIL = 28, + RESULT_CURL_USER_CANCEL = 29, + RESULT_GUI_WINDOW_INIT_FAIL = 30, + RESULT_NO_UPDATE_NECESSARY = 31, + RESULT_LO_MISMATCH = 32, + RESULT_NO_MEM = 33, + RESULT_INVALID_ARGS = 34, + RESULT_NETWORK_FAIL = 35, + RESULT_NO_INTERNET_CONNECTION = 36, + RESULT_NO_TAG_MAP = 37, + RESULT_PLUGINS_FULL = 38, + RESULT_PLUGIN_BEFORE_MASTER = 39, + RESULT_INVALID_SYNTAX = 40 + }; + + enum GameIDs { + AUTODETECT = 0, + OBLIVION = 1, + SKYRIM = 3, + FALLOUT3 = 4, + FALLOUTNV = 5 + }; + }; + private: void syncLoadOrder(); @@ -230,6 +316,9 @@ private: void testMasters(); + void initBoss(); + void convertPluginListForBoss(boost::ptr_vector &inputPlugins, std::vector &activePlugins); + private: std::vector m_ESPs; @@ -253,6 +342,10 @@ private: SignalRefreshed m_Refreshed; + BossDLL *m_BOSS; + boss_db m_BOSSDB; + QTemporaryFile m_TempFile; + }; #endif // PLUGINLIST_H diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 5bff24f5..8412fa27 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -125,11 +125,6 @@ bool PluginListSortProxy::lessThan(const QModelIndex &left, return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; default: { - static bool first = true; - if (first) { - qCritical("invalid sort column %d", left.column()); - first = false; - } return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; } diff --git a/src/profile.cpp b/src/profile.cpp index e075a4e6..c1f1025e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "dummybsa.h" #include "modinfo.h" +#include "safewritefile.h" #include #include #include @@ -149,15 +150,10 @@ void Profile::writeModlistNow(bool onlyOnTimer) const #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") try { - QTemporaryFile file; - - if (!file.open()) { - reportError(tr("failed to open temporary file")); - return; - } + QString fileName = getModlistFileName(); + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { return; } @@ -169,29 +165,17 @@ void Profile::writeModlistNow(bool onlyOnTimer) const ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (modInfo->getFixedPriority() == INT_MIN) { if (m_ModStatus[index].m_Enabled) { - file.write("+"); + file->write("+"); } else { - file.write("-"); + file->write("-"); } - file.write(modInfo->name().toUtf8()); - file.write("\r\n"); + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); } } } - file.close(); - - - QString fileName = getModlistFileName(); - - if (QFile::exists(fileName)) { - shellDeleteQuiet(fileName); - } - - if (!file.copy(fileName)) { - reportError(tr("failed to open \"%1\" for writing").arg(fileName)); - return; - } + file.commit(); qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } catch (const std::exception &e) { diff --git a/src/resources.qrc b/src/resources.qrc index 1582a3f2..73921f64 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -18,7 +18,7 @@ resources/contact-new.png resources/preferences-system.png resources/application-x-executable.png - resources/dialog-information.png + resources/dialog-information.png resources/emblem-readonly.png resources/go-next_16.png resources/go-previous_16.png diff --git a/src/safewritefile.cpp b/src/safewritefile.cpp new file mode 100644 index 00000000..6df8c2b8 --- /dev/null +++ b/src/safewritefile.cpp @@ -0,0 +1,48 @@ +/* +Copyright (C) 2014 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#include "safewritefile.h" +#include + + +using namespace MOBase; + + +SafeWriteFile::SafeWriteFile(const QString &fileName) +: m_FileName(fileName) +{ + if (!m_TempFile.open()) { + throw MyException(QObject::tr("failed to open temporary file")); + } +} + + +QFile *SafeWriteFile::operator->() { + Q_ASSERT(m_TempFile.isOpen()); + return &m_TempFile; +} + + +void SafeWriteFile::commit() { + shellDeleteQuiet(m_FileName); + m_TempFile.rename(m_FileName); + m_TempFile.setAutoRemove(false); + m_TempFile.close(); +} diff --git a/src/safewritefile.h b/src/safewritefile.h new file mode 100644 index 00000000..56bd7744 --- /dev/null +++ b/src/safewritefile.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 2014 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#ifndef SAFEWRITEFILE_H +#define SAFEWRITEFILE_H + + +#include +#include +#include + +/** + * @brief a wrapper for QFile that ensures the file is only actually (over-)written if writing was successful + */ +class SafeWriteFile { +public: + SafeWriteFile(const QString &fileName); + + QFile *operator->(); + + void commit(); + +private: + QString m_FileName; + QTemporaryFile m_TempFile; +}; + + +#endif // SAFEWRITEFILE_H + + + + + -- cgit v1.3.1 From c017f4a0d50b67a44e276bd5ae8929ed3990c62c Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 5 Apr 2014 15:14:37 +0200 Subject: - added buttons to backup and restore the modlist and pluginlist - replaced boss integration with loot --- src/ModOrganizer.pro | 3 +- src/main.cpp | 23 +--- src/mainwindow.cpp | 204 ++++++++++++++++++++++++++++++++- src/mainwindow.h | 16 ++- src/mainwindow.ui | 112 +++++++++++++++++-- src/modlist.cpp | 2 +- src/pluginlist.cpp | 224 +------------------------------------ src/pluginlist.h | 83 +------------- src/profile.h | 6 +- src/resources.qrc | 3 + src/resources/arrange-boxes.png | Bin 0 -> 1634 bytes src/resources/document-save_32.png | Bin 0 -> 1971 bytes src/resources/edit-undo.png | Bin 0 -> 1601 bytes src/selectiondialog.cpp | 123 ++++++++++---------- src/selectiondialog.h | 96 ++++++++-------- src/shared/gameinfo.cpp | 8 ++ src/shared/gameinfo.h | 1 + src/spawn.cpp | 29 ++++- src/spawn.h | 8 +- 19 files changed, 486 insertions(+), 455 deletions(-) create mode 100644 src/resources/arrange-boxes.png create mode 100644 src/resources/document-save_32.png create mode 100644 src/resources/edit-undo.png (limited to 'src/resources.qrc') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 05b05855..62de3f66 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -14,7 +14,8 @@ SUBDIRS = bsatk \ BossDummy \ pythonRunner \ boss_modified \ - esptk + esptk \ + loot_cli plugins.depends = pythonRunner hookdll.depends = shared diff --git a/src/main.cpp b/src/main.cpp index ac903615..d1c5e263 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -80,24 +80,6 @@ using namespace MOBase; using namespace MOShared; -void removeOldLogfiles() -{ - QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"), - QDir::Files, QDir::Name); - - if (files.count() > 5) { - QStringList deleteFiles; - for (int i = 0; i < files.count() - 5; ++i) { - deleteFiles.append(files.at(i).absoluteFilePath()); - } - - if (!shellDelete(deleteFiles)) { - qWarning("failed to remove log files: %s", qPrintable(windowsErrorString(::GetLastError()))); - } - } -} - - // set up required folders (for a first install or after an update or to fix a broken installation) bool bootstrap() { @@ -111,7 +93,7 @@ bool bootstrap() } // cycle logfile - removeOldLogfiles(); + removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name); // create organizer directories QString dirNames[] = { @@ -120,8 +102,7 @@ bool bootstrap() QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss") + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) }; static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bf5ea7f0..87360f3c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -109,8 +109,10 @@ along with Mod Organizer. If not, see . #include #endif #include +#include #include #include +#include #ifdef TEST_MODELS @@ -2649,7 +2651,6 @@ void MainWindow::directory_refreshed() delete oldStructure; refreshDataTree(); - refreshLists(); } else { // TODO: don't know why this happens, this slot seems to get called twice with only one emit return; @@ -5155,20 +5156,213 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } + +void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = NULL; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + qCritical("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + qCritical("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string MainWindow::readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + void MainWindow::on_bossButton_clicked() { try { this->setEnabled(false); ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); - LockedDialog dialog(this, tr("BOSS working"), false); + QProgressDialog dialog(this); + dialog.setLabelText(tr("LOOT working")); + dialog.setMaximum(0); dialog.show(); - qApp->processEvents(); - m_PluginList.bossSort(); - savePluginList(); + QStringList parameters; + parameters << "--game" << ToQString(GameInfo::instance().getGameName()) + << "--gamePath" << ToQString(GameInfo::instance().getGameDirectory()); + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/lootcli.exe"), + parameters.join(" "), + m_CurrentProfile->getName(), + m_Settings.logLevel(), + qApp->applicationDirPath(), + true, + stdOutWrite); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + if (loot != INVALID_HANDLE_VALUE) { + while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) { + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + if (dialog.wasCanceled()) { + ::TerminateProcess(loot, 1); + } + std::string lootOut = readFromPipe(stdOutRead); + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + foreach (const std::string &line, lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t reportidx = line.find("[report]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (reportidx != std::string::npos) { + qDebug("report at %s", line.substr(reportidx + 9).c_str()); + } else { + qDebug("%s", line.c_str()); + } + } + } + } + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + qDebug("%s", remainder.c_str()); + } + + refreshESPList(); + + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + } + dialog.hide(); } catch (const std::exception &e) { reportError(tr("failed to run boss: %1").arg(e.what())); ui->bossButton->setEnabled(false); } } + + +const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; +const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; +const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; + + +bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +{ + QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); + if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { + QFileInfo fileInfo(filePath); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + return true; + } else { + return false; + } +} + +void MainWindow::on_saveButton_clicked() +{ + savePluginList(); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getPluginsFileName(), now) + && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) + && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { + MessageDialog::showMessage(tr("Backup of load order created"), this); + } +} + +QString MainWindow::queryRestore(const QString &filePath) +{ + QFileInfo pluginFileInfo(filePath); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + + SelectionDialog dialog(tr("Choose backup to restore"), this); + QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); + QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); + foreach(const QFileInfo &info, files) { + if (exp.exactMatch(info.fileName())) { + QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); + dialog.addChoice(time.toString(), "", exp.cap(1)); + } else if (exp2.exactMatch(info.fileName())) { + dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); + } + } + + if (dialog.numChoices() == 0) { + QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + return QString(); + } + + if (dialog.exec() == QDialog::Accepted) { + return dialog.getChoiceData().toString(); + } else { + return QString(); + } +} + +void MainWindow::on_restoreButton_clicked() +{ + QString pluginName = m_CurrentProfile->getPluginsFileName(); + QString choice = queryRestore(pluginName); + if (!choice.isEmpty()) { + QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); + QString lockedName = m_CurrentProfile->getLockedOrderFileName(); + if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || + !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshESPList(); + } +} + +void MainWindow::on_saveModsButton_clicked() +{ + m_CurrentProfile->writeModlistNow(true); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { + MessageDialog::showMessage(tr("Backup of modlist created"), this); + } +} +void MainWindow::on_restoreModsButton_clicked() +{ + QString modlistName = m_CurrentProfile->getModlistFileName(); + QString choice = queryRestore(modlistName); + if (!choice.isEmpty()) { + if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshModList(false); + } +} + diff --git a/src/mainwindow.h b/src/mainwindow.h index 23923677..c5b4a8d3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -154,6 +154,8 @@ public: void saveArchiveList(); + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); public slots: void displayColumnSelection(const QPoint &pos); @@ -207,8 +209,6 @@ private: bool nexusLogin(); - void saveCurrentESPList(); - bool testForSteam(); void startSteam(); @@ -290,11 +290,18 @@ private: void activateProxy(bool activate); void installTranslator(const QString &name); + bool createBackup(const QString &filePath, const QDateTime &time); + QString queryRestore(const QString &filePath); + private: static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; + static const char *PATTERN_BACKUP_GLOB; + static const char *PATTERN_BACKUP_REGEX; + static const char *PATTERN_BACKUP_DATE; + private: Ui::MainWindow *ui; @@ -574,6 +581,11 @@ private slots: // ui slots void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); void on_bossButton_clicked(); + + void on_saveButton_clicked(); + void on_restoreButton_clicked(); + void on_restoreModsButton_clicked(); + void on_saveModsButton_clicked(); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 26cbbf83..dc91121c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -115,7 +115,7 @@ 2
- + @@ -170,6 +170,47 @@ p, li { white-space: pre-wrap; } + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + @@ -617,6 +658,68 @@ p, li { white-space: pre-wrap; } 0 + + + + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + @@ -725,13 +828,6 @@ p, li { white-space: pre-wrap; } - - - - Sort - - -
diff --git a/src/modlist.cpp b/src/modlist.cpp index e2cb7cf0..836406e4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -669,7 +669,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa } if (source.count() != 0) { - shellMove(source, target, NULL); + shellMove(source, target); } return true; diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index af6f42da..1f4ed9b1 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -78,7 +78,6 @@ PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent) , m_FontMetrics(QFont()) , m_SaveTimer(this) - , m_BOSS(NULL) { m_SaveTimer.setSingleShot(true); connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); @@ -95,11 +94,6 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { - if (m_BOSS != NULL) { - m_BOSS->CleanUpAPI(); - delete m_BOSS; - m_BOSS = NULL; - } } @@ -595,225 +589,9 @@ void PluginList::refreshLoadOrder() } -class boss_exception : public std::runtime_error { -public: - boss_exception(const std::string &message) : std::runtime_error(message) {} -}; - -#define THROW_BOSS_ERROR(obj) \ - uint8_t *message; \ - obj->GetLastErrorDetails(&message); \ - throw boss_exception(std::string(reinterpret_cast(message))); - -#define U8(text) reinterpret_cast(text) - - -void outputBossLog(const QString &filename) -{ - QFile file(filename); - if (file.open(QIODevice::ReadOnly)) { - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - if (line.length() < 1) - continue; - - int endFirstWord = line.indexOf(':'); - if (endFirstWord < 0) { - endFirstWord = 0; - } - QString firstWord = line.mid(0, endFirstWord); - if (firstWord == "DEBUG") { - qDebug("(boss) %s", line.mid(endFirstWord + 2).constData()); - } else { - qWarning("(boss) %s", line.mid(endFirstWord + 2).constData()); - } - } - } - file.resize(0); -} - - -boss_db PluginList::initBoss() -{ - boss_db result; - bool firstRun = (m_BOSS == nullptr); - if (firstRun) { - m_BOSS = new BossDLL(TEXT("dlls\\boss.dll")); - - if (!m_BOSS->IsCompatibleVersion(2,1,1)) { - throw MyException(tr("BOSS dll incompatible")); - } - uint8_t *versionString; - if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - qDebug("using boss version %s", versionString); - - m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name - m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4); - } - - if (m_BOSS->CreateBossDb(&result, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) { - uint8_t *message; - m_BOSS->GetLastErrorDetails(&message); - std::string messageCopy(reinterpret_cast(message)); - delete m_BOSS; - m_BOSS = NULL; - throw boss_exception(messageCopy); - } - qApp->processEvents(); - - QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt"); - - if (firstRun) { - uint32_t res = m_BOSS->UpdateMasterlist(result, U8(masterlistName.toUtf8().constData())); - qApp->processEvents(); - if (res == BossDLL::RESULT_OK) { - qDebug("boss masterlist updated"); - } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) { - qDebug("boss masterlist already up-to-date"); - } else { - THROW_BOSS_ERROR(m_BOSS) - } - } - if (m_BOSS->Load(result, - U8(masterlistName.toUtf8().constData()), - U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - return result; -} - -void PluginList::convertPluginListForBoss(boss_db db, boost::ptr_vector &inputPlugins, std::vector &activePlugins) -{ - foreach (int idx, m_ESPsByPriority) { - QString fileName = m_ESPs[idx].m_Name; - QByteArray name = fileName.toUtf8(); - - uint8_t *nameU8 = new uint8_t[name.length() + 1]; - memcpy(nameU8, name.constData(), name.length() + 1); - if (m_ESPs[idx].m_Enabled) { - activePlugins.push_back(nameU8); - } - inputPlugins.push_back(nameU8); - } - if (m_BOSS->SetActivePluginsDumb(db, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } -} - -void PluginList::applyBOSSSorting(boss_db db, std::map &lockedLoadOrder, uint8_t **pluginList, size_t size, - int &priority, int &loadOrder, bool recognized, const char *extension) -{ - for (size_t i = 0; i < size; ++i) { - QString name = QString::fromUtf8(reinterpret_cast(pluginList[i])).toLower(); - if (name.endsWith(extension)) { - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - // boss seems to report plugins from userlist as sorted that aren't even installed - continue; - } - - BossMessage *message; - size_t numMessages = 0; - m_BOSS->GetPluginMessages(db, pluginList[i], &message, &numMessages); - BossInfo newInfo; - for (size_t im = 0; im < numMessages; ++im) { - newInfo.m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast(message[im].message))); - } - newInfo.m_BOSSUnrecognized = !recognized; - m_BossInfo[name] = newInfo; - - // locked order plugins are not inserted by boss sorting ... - if (m_LockedOrder.find(name) != m_LockedOrder.end()) { - continue; - } - - // ... but by their enforced priority - while (lockedLoadOrder.find(loadOrder) != lockedLoadOrder.end()) { - auto lloIter = lockedLoadOrder.find(loadOrder); - auto nameIter = m_ESPsByName.find(lloIter->second); - if (nameIter != m_ESPsByName.end()) { - m_ESPs[nameIter->second].m_Priority = priority++; - if (m_ESPs[nameIter->second].m_Enabled) { - m_ESPs[nameIter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[nameIter->second].m_LoadOrder = -1; - } - } - lockedLoadOrder.erase(lloIter); - } - - m_ESPs[iter->second].m_Priority = priority++; - if (m_ESPs[iter->second].m_Enabled) { - m_ESPs[iter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[iter->second].m_LoadOrder = -1; - } - } - } -} - -void PluginList::bossSort() +void PluginList::lootSort() { - boss_db db = initBoss(); - ON_BLOCK_EXIT([&] { m_BOSS->DestroyBossDb(db); }); - - // create a boss-compatible representation of our current mod list. - boost::ptr_vector inputPlugins; - std::vector activePlugins; - convertPluginListForBoss(db, inputPlugins, activePlugins); - - // sort mods in-memory - uint8_t **sortedPlugins; - uint8_t **unrecognizedPlugins; - size_t sizeSorted, sizeUnrecognized; - if (m_BOSS->SortCustomMods(db, - inputPlugins.c_array(), inputPlugins.size(), - &sortedPlugins, &sizeSorted, - &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - - // output the log from boss to our own log to make it visible - outputBossLog(m_TempFile.fileName()); - - qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized); - ChangeBracket layoutChange(this); - - std::map lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair &ele) { lockedLoadOrder[ele.second] = ele.first; }); - - int priority = 0; - int loadOrder = 0; - applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esm"); - applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esm"); - applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esp"); - applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esp"); - - // applyBOSSSorting removed entries from lockedLoadOrder when they were inserted so everything that's left is plugins - // locked to the end of the list. Now this inserts the rest in the ascending priority order. If the list is too short - // to place the plugins in their intended position then this guarantees plugins are kept in the correct relative order - // but it doesn't minimize the number of misplaced plugins. - for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { - auto nameIter = m_ESPsByName.find(iter->second); - if (nameIter != m_ESPsByName.end()) { - m_ESPs[nameIter->second].m_Priority = priority++; - if (m_ESPs[nameIter->second].m_Enabled) { - m_ESPs[nameIter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[nameIter->second].m_LoadOrder = -1; - } - } - } - - // inform view of the changed data - updateIndices(); - layoutChange.finish(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); - m_Refreshed(); } IPluginList::PluginState PluginList::state(const QString &name) const diff --git a/src/pluginlist.h b/src/pluginlist.h index fff2a595..5e8557e2 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -181,7 +181,7 @@ public: void refreshLoadOrder(); - void bossSort(); + void lootSort(); public: virtual PluginState state(const QString &name) const; @@ -204,7 +204,6 @@ public: // implementation of the QAbstractTableModel interface virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; virtual QModelIndex parent(const QModelIndex &child) const; - void applyBOSSSorting(boss_db db, std::map &lockedLoadOrder, uint8_t **pluginList, size_t size, int &priority, int &loadOrder, bool recognized, const char *extension); public slots: /** @@ -260,82 +259,6 @@ private: friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); - class BossDLL : public PDLL { - DECLARE_CLASS(BossDLL) - - DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*) - DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db) - DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI) - - DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*) - DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t) - - DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*) - - DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*) - DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*) - - DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t) - DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**) - - DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**) - DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t) - - DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*) - - enum ResultCode { - RESULT_OK = 0, - RESULT_NO_MASTER_FILE = 1, - RESULT_FILE_READ_FAIL = 2, - RESULT_FILE_WRITE_FAIL = 3, - RESULT_FILE_NOT_UTF8 = 4, - RESULT_FILE_NOT_FOUND = 5, - RESULT_FILE_PARSE_FAIL = 6, - RESULT_CONDITION_EVAL_FAIL = 7, - RESULT_REGEX_EVAL_FAIL = 8, - RESULT_NO_GAME_DETECTED = 9, - RESULT_ENCODING_CONVERSION_FAIL = 10, - RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11, - RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12, - RESULT_READ_UPDATE_FILE_LIST_FAIL = 13, - RESULT_FILE_CRC_MISMATCH = 14, - RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15, - RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16, - RESULT_FS_FILE_RENAME_FAIL = 17, - RESULT_FS_FILE_DELETE_FAIL = 18, - RESULT_FS_CREATE_DIRECTORY_FAIL = 19, - RESULT_FS_ITER_DIRECTORY_FAIL = 20, - RESULT_CURL_INIT_FAIL = 21, - RESULT_CURL_SET_ERRBUFF_FAIL = 22, - RESULT_CURL_SET_OPTION_FAIL = 23, - RESULT_CURL_SET_PROXY_FAIL = 24, - RESULT_CURL_SET_PROXY_TYPE_FAIL = 25, - RESULT_CURL_SET_PROXY_AUTH_FAIL = 26, - RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27, - RESULT_CURL_PERFORM_FAIL = 28, - RESULT_CURL_USER_CANCEL = 29, - RESULT_GUI_WINDOW_INIT_FAIL = 30, - RESULT_NO_UPDATE_NECESSARY = 31, - RESULT_LO_MISMATCH = 32, - RESULT_NO_MEM = 33, - RESULT_INVALID_ARGS = 34, - RESULT_NETWORK_FAIL = 35, - RESULT_NO_INTERNET_CONNECTION = 36, - RESULT_NO_TAG_MAP = 37, - RESULT_PLUGINS_FULL = 38, - RESULT_PLUGIN_BEFORE_MASTER = 39, - RESULT_INVALID_SYNTAX = 40 - }; - - enum GameIDs { - AUTODETECT = 0, - OBLIVION = 1, - SKYRIM = 3, - FALLOUT3 = 4, - FALLOUTNV = 5 - }; - }; - private: void syncLoadOrder(); @@ -353,9 +276,6 @@ private: void testMasters(); - boss_db initBoss(); - void convertPluginListForBoss(boss_db db, boost::ptr_vector &inputPlugins, std::vector &activePlugins); - private: std::vector m_ESPs; @@ -381,7 +301,6 @@ private: SignalRefreshed m_Refreshed; - BossDLL *m_BOSS; QTemporaryFile m_TempFile; }; diff --git a/src/profile.h b/src/profile.h index 4960671a..5aa77357 100644 --- a/src/profile.h +++ b/src/profile.h @@ -159,6 +159,11 @@ public: */ QString getLockedOrderFileName() const; + /** + * @return the path of the modlist file in this profile + */ + QString getModlistFileName() const; + /** * @return path of the archives file in this profile */ @@ -301,7 +306,6 @@ private: void updateIndices(); - QString getModlistFileName() const; void copyFilesTo(QString &target) const; std::vector splitDZString(const wchar_t *buffer) const; diff --git a/src/resources.qrc b/src/resources.qrc index 73921f64..bf53ea95 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -52,5 +52,8 @@ resources/x-office-calendar.png resources/dialog-warning_16.png resources/mail-attachment.png + resources/document-save_32.png + resources/edit-undo.png + resources/arrange-boxes.png diff --git a/src/resources/arrange-boxes.png b/src/resources/arrange-boxes.png new file mode 100644 index 00000000..b1ab67cf Binary files /dev/null and b/src/resources/arrange-boxes.png differ diff --git a/src/resources/document-save_32.png b/src/resources/document-save_32.png new file mode 100644 index 00000000..db5c52b7 Binary files /dev/null and b/src/resources/document-save_32.png differ diff --git a/src/resources/edit-undo.png b/src/resources/edit-undo.png new file mode 100644 index 00000000..61b2ce9a Binary files /dev/null and b/src/resources/edit-undo.png differ diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index dbc10791..902b4d9c 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -17,62 +17,67 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "selectiondialog.h" -#include "ui_selectiondialog.h" - -#include - -SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) - : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) -{ - ui->setupUi(this); - - ui->descriptionLabel->setText(description); -} - -SelectionDialog::~SelectionDialog() -{ - delete ui; -} - - -void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) -{ - QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - - -QVariant SelectionDialog::getChoiceData() -{ - return m_Choice->property("data"); -} - - -QString SelectionDialog::getChoiceString() -{ - if ((m_Choice == NULL) || - (m_ValidateByData && !m_Choice->property("data").isValid())) { - return QString(); - } else { - return m_Choice->text(); - } -} - - -void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) -{ - m_Choice = button; - if (!m_ValidateByData || m_Choice->property("data").isValid()) { - this->accept(); - } else { - this->reject(); - } -} - -void SelectionDialog::on_cancelButton_clicked() -{ - this->reject(); -} +#include "selectiondialog.h" +#include "ui_selectiondialog.h" + +#include + +SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) + : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) +{ + ui->setupUi(this); + + ui->descriptionLabel->setText(description); +} + +SelectionDialog::~SelectionDialog() +{ + delete ui; +} + + +void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) +{ + QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); + button->setProperty("data", data); + ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); + if (data.isValid()) m_ValidateByData = true; +} + +int SelectionDialog::numChoices() const +{ + return ui->buttonBox->findChildren(QString()).count(); +} + + +QVariant SelectionDialog::getChoiceData() +{ + return m_Choice->property("data"); +} + + +QString SelectionDialog::getChoiceString() +{ + if ((m_Choice == NULL) || + (m_ValidateByData && !m_Choice->property("data").isValid())) { + return QString(); + } else { + return m_Choice->text(); + } +} + + +void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + m_Choice = button; + if (!m_ValidateByData || m_Choice->property("data").isValid()) { + this->accept(); + } else { + this->reject(); + } +} + +void SelectionDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/selectiondialog.h b/src/selectiondialog.h index a2844e97..fc04291e 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -17,50 +17,52 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include -#include - -namespace Ui { -class SelectionDialog; -} - -class SelectionDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit SelectionDialog(const QString &description, QWidget *parent = 0); - - ~SelectionDialog(); - - /** - * @brief add a choice to the dialog - * @param buttonText the text to be displayed on the button - * @param description the description that shows up under in small letters inside the button - * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) - * all buttons that contain no data will be treated as "cancel" buttons - */ - void addChoice(const QString &buttonText, const QString &description, const QVariant &data); - - QVariant getChoiceData(); - QString getChoiceString(); - -private slots: - - void on_buttonBox_clicked(QAbstractButton *button); - - void on_cancelButton_clicked(); - -private: - - Ui::SelectionDialog *ui; - QAbstractButton *m_Choice; - bool m_ValidateByData; - -}; - -#endif // SELECTIONDIALOG_H +#ifndef SELECTIONDIALOG_H +#define SELECTIONDIALOG_H + +#include +#include + +namespace Ui { +class SelectionDialog; +} + +class SelectionDialog : public QDialog +{ + Q_OBJECT + +public: + + explicit SelectionDialog(const QString &description, QWidget *parent = 0); + + ~SelectionDialog(); + + /** + * @brief add a choice to the dialog + * @param buttonText the text to be displayed on the button + * @param description the description that shows up under in small letters inside the button + * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) + * all buttons that contain no data will be treated as "cancel" buttons + */ + void addChoice(const QString &buttonText, const QString &description, const QVariant &data); + + int numChoices() const; + + QVariant getChoiceData(); + QString getChoiceString(); + +private slots: + + void on_buttonBox_clicked(QAbstractButton *button); + + void on_cancelButton_clicked(); + +private: + + Ui::SelectionDialog *ui; + QAbstractButton *m_Choice; + bool m_ValidateByData; + +}; + +#endif // SELECTIONDIALOG_H diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index 00bb42fd..b580a226 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -172,6 +172,14 @@ std::wstring GameInfo::getLogDir() const } +std::wstring GameInfo::getLootDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << "\\loot"; + return temp.str(); +} + + std::wstring GameInfo::getTutorialDir() const { std::wostringstream temp; diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 10775e6c..89c9402d 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -113,6 +113,7 @@ public: virtual std::wstring getCacheDir() const; virtual std::wstring getOverwriteDir() const; virtual std::wstring getLogDir() const; + virtual std::wstring getLootDir() const; virtual std::wstring getTutorialDir() const; virtual bool requiresBSAInvalidation() const { return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index b1c5e963..6adafba0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -36,10 +36,23 @@ using namespace MOShared; static const int BUFSIZE = 4096; -bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, HANDLE& processHandle, HANDLE& threadHandle) +bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, + HANDLE stdOut, HANDLE stdErr, + HANDLE& processHandle, HANDLE& threadHandle) { + BOOL inheritHandles = FALSE; STARTUPINFO si; ::ZeroMemory(&si, sizeof(si)); + if (stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + if (stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } si.cb = sizeof(si); int length = wcslen(binary) + wcslen(arguments) + 4; wchar_t *commandLine = NULL; @@ -74,7 +87,7 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus BOOL success = ::CreateProcess(NULL, commandLine, NULL, NULL, // no special process or thread attributes - FALSE, // don't inherit handle + inheritHandles, // inherit handles if we plan to use stdout or stderr reroute suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL NULL, // same environment as parent currentDirectory, // current directory @@ -95,14 +108,22 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus } -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString& profileName, int logLevel, const QDir ¤tDirectory, bool hooked) +HANDLE startBinary(const QFileInfo &binary, + const QString &arguments, + const QString& profileName, + int logLevel, + const QDir ¤tDirectory, + bool hooked, + HANDLE stdOut, + HANDLE stdErr) { HANDLE processHandle, threadHandle; std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); try { - if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, processHandle, threadHandle)) { + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, + stdOut, stdErr, processHandle, threadHandle)) { reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); return INVALID_HANDLE_VALUE; } diff --git a/src/spawn.h b/src/spawn.h index 48320fea..3f037119 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -52,11 +52,17 @@ private: * @param arguments arguments to pass to the binary * @param profileName name of the active profile * @param currentDirectory the directory to use as the working directory to run in + * @param logLevel log level to be used by the hook library. Ignored if hooked is false * @param hooked if set, the binary is started with mo injected + * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process + * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process * @return the process handle * @todo is the profile name even used any more? * @todo is the hooked parameter used? **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, const QDir ¤tDirectory, bool hooked); +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, + const QDir ¤tDirectory, bool hooked, + HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); #endif // SPAWN_H + -- cgit v1.3.1