summaryrefslogtreecommitdiff
path: root/src/pluginlist.cpp
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 /src/pluginlist.cpp
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
Diffstat (limited to 'src/pluginlist.cpp')
-rw-r--r--src/pluginlist.cpp270
1 files changed, 231 insertions, 39 deletions
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));