Compare commits

...
Author SHA1 Message Date
Tannin 6e84aac467 - minor text fixes
- versions without a subminor version are now displayed without it (1.1 instead of 1.1.0)
- version compares now prefer the decimal comparison over the traditional
2013-12-07 16:07:28 +01:00
Tannin 2b711162ff - archive.dll now supports querying the crc value of files
- esptk now determines if a esp is a dummy (without records)
- hook.dll no longer creates a log file if noone wrote to it
- nexus id and installtime columns are now hidden by default
- modlist can now be refreshed without saving first (so plugins can replace the modlist.txt as a whole)
- plugins can now query more details about virtualised files
- added style options "plastique" and "cleanlooks"
- "overwrite" is no longer listed with a creation time
- a warning will now be displayed if the user has too many plugins active
- a warning will now be displayed if mods with scripts have an installation order that doesn't match the corresponding esp load order
- nmm importer now has select all/deselect all buttons
- nmm importer no longer tries to unpack missing files from archives (won't work anyway)
- initial support for importing from nmm 0.5 alpha
- removed some broken warning suppresions
- python runner now works with bundled python
- extended qbs build system (still fails to build the main gui application)
- implemented a nsis-based installer
2013-11-18 20:17:14 +01:00
Tannin b9aa3d7668 - diagnosis plugins can now request to be re-evaluated
- main application now contains means for plugins to react on some changes (to be extended)
- plugins can now retrieve more information about a mod
- user-agent sent to nexus now automatically contains the current MO version
- included updated russian translation
- problems dialog now refreshes itself after a fix was applied
- fixed new esp/asset ordering diagnosis. May be fully functional now
- bugfix: restoring locked load order could leave MO in an endless loop (not sure if this fix is correct yet)
- bugfix: plugin list was not visually updated after some changes
- bugfix: nmm importer threw away mod ordering
2013-11-06 18:35:27 +01:00
Tannin d4a7f6fcb9 - added a new diagnosis to detect potential problems in regards to esp vs. asset ordering (not fully functional yet)
- new plugin interfaces to the mod list
- plugins can now query the origin (mod) of a esp/esm
- bugfix: potential access to profile before one was activated
- bugfix: regression in previous (not-yet-released) commit prevented changes to bsa list from being saved
2013-11-01 14:59:25 +01:00
Tannin 49c1251f77 diagnosis plugin now warns when nitpick is installed 2013-09-30 18:33:45 +02:00
3 changed files with 313 additions and 7 deletions
+3 -2
View File
@@ -4,8 +4,6 @@
#
#-------------------------------------------------
QT -= gui
TARGET = diagnoseBasic
TEMPLATE = lib
@@ -15,6 +13,9 @@ CONFIG += dll
DEFINES += DIAGNOSEBASIC_LIBRARY
DEFINES += NOMINMAX
# suppress a few warnings caused by boost vs vc++ paranoia
DEFINES += _SCL_SECURE_NO_WARNINGS
SOURCES += diagnosebasic.cpp
HEADERS += diagnosebasic.h
+261 -4
View File
@@ -1,3 +1,22 @@
/*
Copyright (C) 2013 Sebastian Herbord. All rights reserved.
This file is part of the basic diagnosis plugin for Mod Organizer
This plugin 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.
This plugin 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 this plugin. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagnosebasic.h"
#include <report.h>
#include <utility.h>
@@ -5,8 +24,11 @@
#include <QFile>
#include <QDir>
#include <QCoreApplication>
#include <QMessageBox>
#include <QDateTime>
#include <regex>
#include <boost/assign.hpp>
#include <functional>
using namespace MOBase;
@@ -19,6 +41,11 @@ DiagnoseBasic::DiagnoseBasic()
bool DiagnoseBasic::init(IOrganizer *moInfo)
{
m_MOInfo = moInfo;
m_MOInfo->modList()->onModStateChanged([&] (const QString &modName, IModList::ModStates) {
if (modName == "Overwrite") invalidate(); });
m_MOInfo->pluginList()->onRefreshed([&] () { this->invalidate(); });
return true;
}
@@ -39,7 +66,7 @@ QString DiagnoseBasic::description() const
VersionInfo DiagnoseBasic::version() const
{
return VersionInfo(1, 0, 0, VersionInfo::RELEASE_FINAL);
return VersionInfo(1, 1, 0, VersionInfo::RELEASE_FINAL);
}
bool DiagnoseBasic::isActive() const
@@ -108,6 +135,158 @@ bool DiagnoseBasic::overwriteFiles() const
return dir.count() != 2; // account for . and ..
}
bool DiagnoseBasic::nitpickInstalled() const
{
QString path = m_MOInfo->resolvePath("skse/plugins/nitpick.dll");
return !path.isEmpty();
}
/// unused code to remove duplicates from a vector
template<typename T>
void makeUnique(std::vector<T> &vector)
{
std::set<T> done;
auto read = vector.begin();
auto write = vector.begin();
for (; read != vector.end(); ++read) {
if (done.insert(*read).second) {
*write = *read;
++write;
}
}
vector.erase(write, vector.end());
}
bool operator<(const DiagnoseBasic::Move &lhs, const DiagnoseBasic::Move &rhs) {
if (lhs.item.modName != rhs.item.modName) return lhs.item.modName < rhs.item.modName;
else return lhs.reference.modName < rhs.reference.modName;
}
bool DiagnoseBasic::assetOrder() const
{
struct Sorter {
struct {
int operator()(const ListElement &lhs, const ListElement &rhs) {
return lhs.modPriority < rhs.modPriority;
}
} minMod;
std::vector <Move> moves;
void operator()(const std::vector<ListElement> &list) {
if (list.size() == 0) {
return;
}
// generate a copy of list that contains each mod only once, otherwise
// strange things happen if a mod contains multiple esps that are mixed
// with esps from other mods
std::vector<ListElement> modList;
{
std::set<QString> includedMods;
foreach(const ListElement &ele, list) {
if (includedMods.find(ele.modName) == includedMods.end()) {
modList.push_back(ele);
includedMods.insert(ele.modName);
}
}
}
std::vector<ListElement> sorted;
{
auto maxSeqBegin = modList.end();
auto maxSeqEnd = modList.end();
// first, determine the longest sequence of correctly sorted mods
auto curSeqBegin = modList.begin();
auto curSeqEnd = modList.begin();
auto iter = modList.begin() + 1;
for (; iter != modList.end(); ++iter) {
if (iter->modPriority < curSeqEnd->modPriority) {
// sequence ends
if ((maxSeqBegin == modList.end()) || ((curSeqEnd - curSeqBegin) > (maxSeqEnd - maxSeqBegin))) {
maxSeqBegin = curSeqBegin;
maxSeqEnd = iter;
}
curSeqBegin = curSeqEnd = iter;
} else {
curSeqEnd = iter;
}
}
if ((maxSeqBegin == modList.end()) || ((curSeqEnd - curSeqBegin) > (maxSeqEnd - maxSeqBegin))) {
maxSeqBegin = curSeqBegin;
maxSeqEnd = modList.end();
}
sorted = std::vector<ListElement>(maxSeqBegin, maxSeqEnd);
modList.erase(maxSeqBegin, maxSeqEnd);
}
// now move the elements NOT in this sequence to the correct location within
while (modList.begin() != modList.end()) {
auto iter = modList.begin();
bool found = false;
auto targetIter = sorted.begin();
for (; targetIter != sorted.end(); ++targetIter) {
if (targetIter->pluginPriority > iter->pluginPriority) {
moves.push_back(Move(*iter, *targetIter, Move::BEFORE));
found = true;
break;
}
}
if (!found) {
// add to end!
moves.push_back(Move(*iter, *sorted.rbegin(), Move::AFTER));
}
sorted.insert(targetIter, *iter);
modList.erase(iter);
}
}
} minSorter;
std::vector<ListElement> list;
// list of mods containing conflicted scripts. We care only for those
std::set<QString> scriptMods;
foreach (const IOrganizer::FileInfo &pex, m_MOInfo->findFileInfos("scripts",
[] (const IOrganizer::FileInfo &file) -> bool { return file.filePath.endsWith(".pex", Qt::CaseInsensitive); })) {
if (pex.origins.size() > 1) {
foreach (const QString &origin, pex.origins) {
scriptMods.insert(origin);
}
}
}
// produce a list with the information we need: plugin, mod and the priority for each
QStringList esps = m_MOInfo->findFiles("", [] (const QString &fileName) -> bool { return fileName.endsWith(".esp", Qt::CaseInsensitive); });
foreach (const QString &esp, esps) {
ListElement ele;
ele.espName = QFileInfo(esp).fileName();
ele.modName = m_MOInfo->pluginList()->origin(ele.espName);
ele.pluginPriority = m_MOInfo->pluginList()->priority(ele.espName);
ele.modPriority = m_MOInfo->modList()->priority(ele.modName);
IModList::ModStates state = m_MOInfo->modList()->state(ele.modName);
if (state.testFlag(IModList::STATE_EXISTS) && !state.testFlag(IModList::STATE_ESSENTIAL) &&
(scriptMods.find(ele.modName) != scriptMods.end())) {
list.push_back(ele);
}
}
// sort the list by plugin priority
std::sort(list.begin(), list.end(), [] (const ListElement &lhs, const ListElement &rhs) -> bool { return lhs.pluginPriority < rhs.pluginPriority; });
// now determine the moves necessary to bring the mod list into this order
minSorter(list);
m_SuggestedMoves = minSorter.moves;
return m_SuggestedMoves.size() > 0;
}
bool DiagnoseBasic::invalidFontConfig() const
{
if (m_MOInfo->gameInfo().type() != IGameInfo::TYPE_SKYRIM) {
@@ -166,6 +345,17 @@ std::vector<unsigned int> DiagnoseBasic::activeProblems() const
if (invalidFontConfig()) {
result.push_back(PROBLEM_INVALIDFONT);
}
if (nitpickInstalled()) {
result.push_back(PROBLEM_NITPICKINSTALLED);
}
if (assetOrder()) {
result.push_back(PROBLEM_ASSETORDER);
}
QStringList backups = QDir(m_MOInfo->profilePath()).entryList(QStringList() << "modlist.txt_backup_*");
if (backups.size() > 0) {
m_NewestModlistBackup = backups.last();
result.push_back(PROBLEM_MODLISTBACKUP);
}
return result;
}
@@ -179,6 +369,12 @@ QString DiagnoseBasic::shortDescription(unsigned int key) const
return tr("There are files in your overwrite mod");
case PROBLEM_INVALIDFONT:
return tr("Your font configuration may be broken");
case PROBLEM_NITPICKINSTALLED:
return tr("Nitpick installed");
case PROBLEM_ASSETORDER:
return tr("Potential Mod order problem");
case PROBLEM_MODLISTBACKUP:
return tr("Modlist backup exists");
default:
throw MyException(tr("invalid problem key %1").arg(key));
}
@@ -200,19 +396,80 @@ QString DiagnoseBasic::fullDescription(unsigned int key) const
case PROBLEM_INVALIDFONT:
return tr("Your current configuration seems to reference a font that is not installed. You may see only boxes instead of letters.<br>"
"The font configuration is in Data\\interface\\fontconfig.txt. Most likely you have a broken installation of a font replacer mod.");
case PROBLEM_NITPICKINSTALLED:
return tr("You have the nitpick skse plugin installed. This plugin is not needed with Mod Organizer because MO already offers the same functionality. "
"Worse: The two solutions may conflict so it's strongly suggested you remove this plugin.");
case PROBLEM_ASSETORDER: {
QString res = tr("The conflict resolution order for some mods with scripts differs from the corresponding esp. This can lead to subtle, hard to locate "
"bugs. You should re-order the affected mods. The following changes should fix the issue:") + "<ul>";
foreach (const Move &op, m_SuggestedMoves) {
if (op.type == Move::BEFORE) {
res += "<li>" + tr("Move %1 before %2").arg(op.item.modName).arg(op.reference.modName) + "</li>";
} else {
res += "<li>" + tr("Move %1 after %2").arg(op.item.modName).arg(op.reference.modName) + "</li>";
}
}
res += "</ul>";
return res;
} break;
case PROBLEM_MODLISTBACKUP: {
uint timestamp = m_NewestModlistBackup.right(10).toULong();
QDateTime time;
time.setTime_t(timestamp);
return tr("A previous operation created a backup of your mod list on %1.<br>"
"This backup contains both the info which mods are enabled and the ordering.<br>"
"You can restore that backup here.").arg(time.toString());
} break;
default:
throw MyException(tr("invalid problem key %1").arg(key));
}
}
bool DiagnoseBasic::hasGuidedFix(unsigned int) const
bool DiagnoseBasic::hasGuidedFix(unsigned int key) const
{
return false;
return /*(key == PROBLEM_ASSETORDER) || */(key == PROBLEM_MODLISTBACKUP);
}
void DiagnoseBasic::startGuidedFix(unsigned int key) const
{
throw MyException(tr("invalid problem key %1").arg(key));
switch (key) {
/* case PROBLEM_ASSETORDER: {
if (QMessageBox::warning(NULL, tr("Continue?"), tr("This <b>BETA</b> feature will rearrange your mods to eliminate all "
"possible ordering conflicts. A backup of your mod list will be created. Proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
shellCopy(QStringList(m_MOInfo->profilePath() + "/modlist.txt"),
QStringList(m_MOInfo->profilePath() + "/modlist.txt_backup_" + QString("%1").arg(QDateTime::currentDateTime().toTime_t())));
foreach (const Move &op, m_SuggestedMoves) {
int oldPriority = m_MOInfo->modList()->priority(op.item.modName);
int targetPriority = -1;
if (op.type == Move::BEFORE) {
targetPriority = m_MOInfo->modList()->priority(op.reference.modName);
} else {
targetPriority = m_MOInfo->modList()->priority(op.reference.modName) + 1;
}
if (oldPriority < targetPriority) {
--targetPriority;
}
m_MOInfo->modList()->setPriority(op.item.modName, targetPriority);
}
}
} break;*/
case PROBLEM_MODLISTBACKUP: {
QMessageBox question(QMessageBox::Question, tr("Restore backup?"),
tr("Do you want to restore this backup or delete it?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
question.setButtonText(QMessageBox::Yes, tr("Restore"));
question.setButtonText(QMessageBox::No, tr("Delete"));
question.exec();
if (question.result() == QMessageBox::Yes) {
shellMove(QStringList(m_MOInfo->profilePath() + "/" + m_NewestModlistBackup), QStringList(m_MOInfo->profilePath() + "/modlist.txt"));
m_MOInfo->refreshModList(false);
} else if (question.result() == QMessageBox::No) {
shellDelete(QStringList(m_MOInfo->profilePath() + "/" + m_NewestModlistBackup));
}
} break;
default: throw MyException(tr("invalid problem key %1").arg(key));
}
}
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
+49 -1
View File
@@ -1,3 +1,22 @@
/*
Copyright (C) 2013 Sebastian Herbord. All rights reserved.
This file is part of basic diagnosis plugin for MO
This plugin 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.
This plugin 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 this plugin. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DIAGNOSEBASIC_H
#define DIAGNOSEBASIC_H
@@ -5,6 +24,7 @@
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <imoinfo.h>
#include <imodlist.h>
class DiagnoseBasic : public QObject, MOBase::IPlugin, MOBase::IPluginDiagnose
@@ -42,19 +62,47 @@ private:
bool errorReported() const;
bool overwriteFiles() const;
bool invalidFontConfig() const;
bool nitpickInstalled() const;
bool assetOrder() const;
private:
static const unsigned int PROBLEM_ERRORLOG = 1;
static const unsigned int PROBLEM_OVERWRITE = 2;
static const unsigned int PROBLEM_INVALIDFONT = 3;
static const unsigned int PROBLEM_NITPICKINSTALLED = 4;
static const unsigned int PROBLEM_ASSETORDER = 5;
static const unsigned int PROBLEM_MODLISTBACKUP = 6;
static const unsigned int NUM_CONTEXT_ROWS = 5;
private:
const MOBase::IOrganizer *m_MOInfo;
struct ListElement {
QString espName;
QString modName;
int pluginPriority;
int modPriority;
};
struct Move {
ListElement item;
ListElement reference;
enum EType {
BEFORE,
AFTER
} type;
Move(const ListElement &initItem, const ListElement &initReference, EType initType)
: item(initItem), reference(initReference), type(initType) {}
};
friend bool operator<(const Move &lhs, const Move &rhs);
private:
MOBase::IOrganizer *m_MOInfo;
mutable QString m_ErrorMessage;
mutable std::vector <Move> m_SuggestedMoves;
mutable QString m_NewestModlistBackup;
};