mirror of
https://github.com/ModOrganizer2/modorganizer-diagnose_basic.git
synced 2026-07-27 14:03:10 -07:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e84aac467 | ||
|
|
2b711162ff | ||
|
|
b9aa3d7668 | ||
|
|
d4a7f6fcb9 |
@@ -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
|
||||
|
||||
+247
-4
@@ -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
|
||||
@@ -114,6 +141,152 @@ bool DiagnoseBasic::nitpickInstalled() const
|
||||
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) {
|
||||
@@ -175,6 +348,14 @@ std::vector<unsigned int> DiagnoseBasic::activeProblems() const
|
||||
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;
|
||||
}
|
||||
@@ -190,6 +371,10 @@ QString DiagnoseBasic::shortDescription(unsigned int key) const
|
||||
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));
|
||||
}
|
||||
@@ -214,19 +399,77 @@ QString DiagnoseBasic::fullDescription(unsigned int key) const
|
||||
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)
|
||||
|
||||
+47
-1
@@ -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
|
||||
@@ -43,6 +63,7 @@ private:
|
||||
bool overwriteFiles() const;
|
||||
bool invalidFontConfig() const;
|
||||
bool nitpickInstalled() const;
|
||||
bool assetOrder() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -50,13 +71,38 @@ private:
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user