summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTannin <devnull@localhost>2014-01-23 00:05:46 +0100
committerTannin <devnull@localhost>2014-01-23 00:05:46 +0100
commite597823337c858f2985c615ee5147f47567991ee (patch)
treee42647cf1114ebd4d42e4a8c15a91b941961c3df
parente69210b3a78c4a6c63086d84e6bdb2c3b47d8944 (diff)
- 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
-rw-r--r--src/ModOrganizer.pro3
-rw-r--r--src/aboutdialog.cpp2
-rw-r--r--src/icondelegate.cpp30
-rw-r--r--src/icondelegate.h5
-rw-r--r--src/lockeddialog.cpp104
-rw-r--r--src/lockeddialog.h96
-rw-r--r--src/logbuffer.cpp3
-rw-r--r--src/main.cpp5
-rw-r--r--src/mainwindow.cpp26
-rw-r--r--src/mainwindow.h1
-rw-r--r--src/mainwindow.ui82
-rw-r--r--src/modflagicondelegate.cpp50
-rw-r--r--src/modflagicondelegate.h18
-rw-r--r--src/organizer.pro32
-rw-r--r--src/pdll.h402
-rw-r--r--src/pluginflagicondelegate.cpp24
-rw-r--r--src/pluginflagicondelegate.h17
-rw-r--r--src/pluginlist.cpp270
-rw-r--r--src/pluginlist.h95
-rw-r--r--src/pluginlistsortproxy.cpp5
-rw-r--r--src/profile.cpp34
-rw-r--r--src/resources.qrc2
-rw-r--r--src/safewritefile.cpp48
-rw-r--r--src/safewritefile.h51
24 files changed, 1179 insertions, 226 deletions
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("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").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<ModInfo::EFlag> flags = info->getFlags();
+
+ QList<QIcon> 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<QIcon> 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 <http://www.gnu.org/licenses/>.
*/
-#include "lockeddialog.h"
-#include "ui_lockeddialog.h"
-#include <QResizeEvent>
-
-
-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 <QResizeEvent>
+
+
+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 <http://www.gnu.org/licenses/>.
*/
-#ifndef LOCKEDDIALOG_H
-#define LOCKEDDIALOG_H
-
-#include <QDialog>
-
-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 <QDialog>
+
+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 <http://www.gnu.org/licenses/>.
#include "report.h"
#include <QMutexLocker>
#include <QFile>
+#include <QDateTime>
#include <Windows.h>
QScopedPointer<LogBuffer> LogBuffer::s_Instance;
@@ -124,8 +125,6 @@ char LogBuffer::msgTypeID(QtMsgType type)
}
}
-#include <QDateTime>
-
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 <http://www.gnu.org/licenses/>.
#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 <http://www.gnu.org/licenses/>.
#include <QtConcurrentRun>
#include <QCoreApplication>
#include <scopeguard.h>
+#include <boost/thread.hpp>
#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 @@
<property name="spacing">
<number>4</number>
</property>
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -705,14 +714,25 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <widget class="MOBase::LineEditClear" name="espFilterEdit">
- <property name="text">
- <string/>
- </property>
- <property name="placeholderText">
- <string>Namefilter</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="MOBase::LineEditClear" name="espFilterEdit">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="placeholderText">
+ <string>Namefilter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="bossButton">
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
@@ -721,7 +741,16 @@ p, li { white-space: pre-wrap; }
<string notr="true">Archives</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -801,7 +830,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<string>Data</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -871,7 +909,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye
<string>Saves</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -900,7 +947,16 @@ p, li { white-space: pre-wrap; }
<string>Downloads</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
- <property name="margin">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
<number>2</number>
</property>
<item>
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 <QList>
+
+
+ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent)
+ : IconDelegate(parent)
+{
+}
+
+QList<QIcon> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
+{
+ QList<QIcon> result;
+ QVariant modid = index.data(Qt::UserRole + 1);
+ if (modid.isValid()) {
+ ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt());
+ std::vector<ModInfo::EFlag> 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<QIcon> 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 <windows.h>
+#include <winbase.h>
+
+#include <windows_error.h>
+#include <tchar.h>
+#include <sstream>
+
+
+#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 <QList>
+
+
+PluginFlagIconDelegate::PluginFlagIconDelegate(QObject *parent)
+ : IconDelegate(parent)
+{
+}
+
+QList<QIcon> PluginFlagIconDelegate::getIcons(const QModelIndex &index) const
+{
+ QList<QIcon> result;
+ foreach (const QVariant &var, index.data(Qt::UserRole + 1).toList()) {
+ result.append(var.value<QIcon>());
+ }
+ 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<QIcon> 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 <http://www.gnu.org/licenses/>.
#include "pluginlist.h"
#include "report.h"
#include "inject.h"
-#include <utility.h>
#include "settings.h"
+#include "safewritefile.h"
+#include "scopeguard.h"
+#include <utility.h>
#include <gameinfo.h>
#include <espfile.h>
#include <windows_error.h>
@@ -39,7 +41,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QKeyEvent>
#include <QSortFilterProxyModel>
-#include <tchar.h>
#include <ctime>
#include <algorithm>
#include <stdexcept>
@@ -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<char*>(message)));
+
+#define U8(text) reinterpret_cast<const uint8_t*>(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<const char*>(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<uint8_t> &inputPlugins, std::vector<uint8_t*> &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<const char*>(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<const char*>(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<uint8_t> inputPlugins;
+ std::vector<uint8_t*> 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("<br>") + "<br><hr>";
+ }
+ if (m_ESPs[index].m_BOSSUnrecognized) {
+ toolTip += "Not recognized by BOSS<br><ht>";
+ }
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 += "<br>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 <http://www.gnu.org/licenses/>.
#include <QString>
#include <QListWidget>
#include <QTimer>
+#include <QTemporaryFile>
#include <boost/signals2.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
#include <vector>
+#include <pdll.h>
+#include <BOSS-API.h>
/**
@@ -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<QString> m_Masters;
mutable std::set<QString> 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<uint8_t> &inputPlugins, std::vector<uint8_t*> &activePlugins);
+
private:
std::vector<ESPInfo> 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 <http://www.gnu.org/licenses/>.
#include "windows_error.h"
#include "dummybsa.h"
#include "modinfo.h"
+#include "safewritefile.h"
#include <utility.h>
#include <util.h>
#include <error_report.h>
@@ -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 @@
<file alias="profiles">resources/contact-new.png</file>
<file alias="settings">resources/preferences-system.png</file>
<file alias="executable">resources/application-x-executable.png</file>
- <file>resources/dialog-information.png</file>
+ <file alias="information">resources/dialog-information.png</file>
<file alias="locked">resources/emblem-readonly.png</file>
<file alias="next">resources/go-next_16.png</file>
<file alias="previous">resources/go-previous_16.png</file>
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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "safewritefile.h"
+#include <QStringList>
+
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#ifndef SAFEWRITEFILE_H
+#define SAFEWRITEFILE_H
+
+
+#include <utility.h>
+#include <QTemporaryFile>
+#include <QString>
+
+/**
+ * @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
+
+
+
+
+