diff options
85 files changed, 8434 insertions, 8243 deletions
diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index acb01f4e..db0bf7fa 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -17,55 +17,55 @@ 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 ACTIVATEMODSDIALOG_H
-#define ACTIVATEMODSDIALOG_H
-
-#include "tutorabledialog.h"
-#include <map>
-#include <set>
-
-namespace Ui {
- class ActivateModsDialog;
-}
-
-/**
- * @brief Dialog that is used to batch activate/deactivate mods and plugins
- **/
-class ActivateModsDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- /**
- * @brief constructor
- *
- * @param missingPlugins a map containing missing plugins that need to be activated
- * @param parent ... Defaults to 0.
- **/
- explicit ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, QWidget *parent = 0);
- ~ActivateModsDialog();
-
- /**
- * @brief get a list of mods that the user chose to activate
- *
- * @note This can of ocurse only be called after the dialog has been displayed
- *
- * @return set< QString > the mods to activate
- **/
- std::set<QString> getModsToActivate();
-
- /**
- * @brief get a list of plugins that should be activated
- *
- * @return set< QString > the plugins to activate. This contains only plugins that become available after enabling the mods retrieved with getModsToActivate
- **/
- std::set<QString> getESPsToActivate();
-
-private slots:
- void on_buttonBox_accepted();
-
-private:
- Ui::ActivateModsDialog *ui;
-};
-
-#endif // ACTIVATEMODSDIALOG_H
+#ifndef ACTIVATEMODSDIALOG_H +#define ACTIVATEMODSDIALOG_H + +#include "tutorabledialog.h" +#include <map> +#include <set> + +namespace Ui { + class ActivateModsDialog; +} + +/** + * @brief Dialog that is used to batch activate/deactivate mods and plugins + **/ +class ActivateModsDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param missingPlugins a map containing missing plugins that need to be activated + * @param parent ... Defaults to 0. + **/ + explicit ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, QWidget *parent = 0); + ~ActivateModsDialog(); + + /** + * @brief get a list of mods that the user chose to activate + * + * @note This can of ocurse only be called after the dialog has been displayed + * + * @return set< QString > the mods to activate + **/ + std::set<QString> getModsToActivate(); + + /** + * @brief get a list of plugins that should be activated + * + * @return set< QString > the plugins to activate. This contains only plugins that become available after enabling the mods retrieved with getModsToActivate + **/ + std::set<QString> getESPsToActivate(); + +private slots: + void on_buttonBox_accepted(); + +private: + Ui::ActivateModsDialog *ui; +}; + +#endif // ACTIVATEMODSDIALOG_H diff --git a/src/baincomplexinstallerdialog.cpp b/src/baincomplexinstallerdialog.cpp index 9b282c82..f1440ff3 100644 --- a/src/baincomplexinstallerdialog.cpp +++ b/src/baincomplexinstallerdialog.cpp @@ -23,6 +23,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDir> + +using namespace MOBase; + + BainComplexInstallerDialog::BainComplexInstallerDialog(DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent) : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), m_Manual(false) { diff --git a/src/baincomplexinstallerdialog.h b/src/baincomplexinstallerdialog.h index c2f74745..136a4e54 100644 --- a/src/baincomplexinstallerdialog.h +++ b/src/baincomplexinstallerdialog.h @@ -17,79 +17,79 @@ 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 BAINCOMPLEXINSTALLERDIALOG_H
-#define BAINCOMPLEXINSTALLERDIALOG_H
-
-
-#include "mytree.h"
-#include "installdialog.h"
-#include "tutorabledialog.h"
-
-
-namespace Ui {
-class BainComplexInstallerDialog;
-}
-
-
-/**
- * @brief Dialog to choose from options offered by a (complex) bain package
- **/
-class BainComplexInstallerDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- /**
- * @brief Constructor
- *
- * @param tree the directory tree of the archive. The caller is resonsible to verify this actually qualifies as a bain installer
- * @param modName proposed name for the mod. The dialog allows the user to change this
- * @param hasPackageTXT set to true if a package.txt is available for this archive. The file has to be unpacked to QDir::tempPath before displaying the dialog!
- * @param parent parent widget
- **/
- explicit BainComplexInstallerDialog(DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent);
- ~BainComplexInstallerDialog();
-
- /**
- * @return bool true if the user requested the manual dialog
- **/
- bool manualRequested() const { return m_Manual; }
-
- /**
- * @return QString the (user-modified) name to be used for the mod
- **/
- QString getName() const;
-
- /**
- * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree.
- *
- * @note This call is destructive on the input tree!
- *
- * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?)
- * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer!
- **/
- DirectoryTree *updateTree(DirectoryTree *tree);
-
-private slots:
-
- void on_manualBtn_clicked();
-
- void on_okBtn_clicked();
-
- void on_cancelBtn_clicked();
-
- void on_packageBtn_clicked();
-
-private:
-
- void moveTreeUp(DirectoryTree *target, DirectoryTree::Node *child);
-
-private:
-
- Ui::BainComplexInstallerDialog *ui;
-
- bool m_Manual;
-
-};
-
-#endif // BAINCOMPLEXINSTALLERDIALOG_H
+#ifndef BAINCOMPLEXINSTALLERDIALOG_H +#define BAINCOMPLEXINSTALLERDIALOG_H + + +#include "mytree.h" +#include "installdialog.h" +#include "tutorabledialog.h" + + +namespace Ui { +class BainComplexInstallerDialog; +} + + +/** + * @brief Dialog to choose from options offered by a (complex) bain package + **/ +class BainComplexInstallerDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * + * @param tree the directory tree of the archive. The caller is resonsible to verify this actually qualifies as a bain installer + * @param modName proposed name for the mod. The dialog allows the user to change this + * @param hasPackageTXT set to true if a package.txt is available for this archive. The file has to be unpacked to QDir::tempPath before displaying the dialog! + * @param parent parent widget + **/ + explicit BainComplexInstallerDialog(MOBase::DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent); + ~BainComplexInstallerDialog(); + + /** + * @return bool true if the user requested the manual dialog + **/ + bool manualRequested() const { return m_Manual; } + + /** + * @return QString the (user-modified) name to be used for the mod + **/ + QString getName() const; + + /** + * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree. + * + * @note This call is destructive on the input tree! + * + * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?) + * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer! + **/ + MOBase::DirectoryTree *updateTree(MOBase::DirectoryTree *tree); + +private slots: + + void on_manualBtn_clicked(); + + void on_okBtn_clicked(); + + void on_cancelBtn_clicked(); + + void on_packageBtn_clicked(); + +private: + + void moveTreeUp(MOBase::DirectoryTree *target, MOBase::DirectoryTree::Node *child); + +private: + + Ui::BainComplexInstallerDialog *ui; + + bool m_Manual; + +}; + +#endif // BAINCOMPLEXINSTALLERDIALOG_H diff --git a/src/categories.cpp b/src/categories.cpp index 133778c4..f8bc3530 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -17,254 +17,258 @@ 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 "categories.h"
-#include <utility.h>
-#include "report.h"
-#include <gameinfo.h>
-#include <QObject>
-#include <QFile>
-#include <QDir>
-#include <QList>
-
-
-CategoryFactory* CategoryFactory::s_Instance = NULL;
-
-
-CategoryFactory::CategoryFactory()
-{
- reset();
-
- QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat"));
-
- if (!categoryFile.open(QIODevice::ReadOnly)) {
- loadDefaultCategories();
- } else {
- int lineNum = 0;
- while (!categoryFile.atEnd()) {
- QByteArray line = categoryFile.readLine();
- ++lineNum;
- QList<QByteArray> cells = line.split('|');
- if (cells.count() != 4) {
- qCritical("invalid category line %d: %s (%d cells)",
- lineNum, line.constData(), cells.count());
- } else {
- std::vector<int> nexusIDs;
- if (cells[2].length() > 0) {
- QList<QByteArray> nexusIDStrings = cells[2].split(',');
- for (QList<QByteArray>::iterator iter = nexusIDStrings.begin();
- iter != nexusIDStrings.end(); ++iter) {
- bool ok = false;
- int temp = iter->toInt(&ok);
- if (!ok) {
- qCritical("invalid id %s", iter->constData());
- }
- nexusIDs.push_back(temp);
- }
- }
- bool cell0Ok = true;
- bool cell3Ok = true;
- int id = cells[0].toInt(&cell0Ok);
- int parentID = cells[3].trimmed().toInt(&cell3Ok);
- if (!cell0Ok || !cell3Ok) {
- qCritical("invalid category line %d: %s",
- lineNum, line.constData());
- }
- addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID);
- }
- }
- categoryFile.close();
- }
- std::sort(m_Categories.begin(), m_Categories.end());
- setParents();
-}
-
-
-CategoryFactory &CategoryFactory::instance()
-{
- if (s_Instance == NULL) {
- s_Instance = new CategoryFactory;
- }
- return *s_Instance;
-}
-
-
-void CategoryFactory::reset()
-{
- m_Categories.clear();
- addCategory(0, "None", MakeVector<int>(2, 28, 87), 0);
-}
-
-
-void CategoryFactory::setParents()
-{
- for (std::vector<Category>::iterator iter = m_Categories.begin();
- iter != m_Categories.end(); ++iter) {
- iter->m_HasChildren = false;
- }
-
- for (std::vector<Category>::const_iterator categoryIter = m_Categories.begin();
- categoryIter != m_Categories.end(); ++categoryIter) {
- if (categoryIter->m_ParentID != 0) {
- std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(categoryIter->m_ParentID);
- if (iter != m_IDMap.end()) {
- m_Categories[iter->second].m_HasChildren = true;
- }
- }
- }
-}
-
-
-void CategoryFactory::saveCategories()
-{
- QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat"));
-
- if (!categoryFile.open(QIODevice::WriteOnly)) {
- reportError(QObject::tr("Failed to save custom categories"));
- return;
- }
-
- categoryFile.resize(0);
- for (std::vector<Category>::const_iterator iter = m_Categories.begin();
- iter != m_Categories.end(); ++iter) {
- if (iter->m_ID == 0) {
- continue;
- }
- QByteArray line;
- line.append(QByteArray::number(iter->m_ID)).append("|")
- .append(iter->m_Name.toUtf8()).append("|")
- .append(VectorJoin(iter->m_NexusIDs, ",")).append("|")
- .append(QByteArray::number(iter->m_ParentID)).append("\n");
- categoryFile.write(line);
- }
- categoryFile.close();
-}
-
-
-void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
-{
- int index = m_Categories.size();
- m_Categories.push_back(Category(index, id, name, nexusIDs, parentID));
- for (std::vector<int>::const_iterator iter = nexusIDs.begin();
- iter != nexusIDs.end(); ++iter) {
- m_NexusMap[*iter] = index;
- }
- m_IDMap[id] = index;
-}
-
-
-void CategoryFactory::loadDefaultCategories()
-{
- // the order here is relevant as it defines the order in which the
- // mods appear in the combo box
- addCategory(1, "Animations", MakeVector<int>(1, 51), 0);
- addCategory(2, "Armour", MakeVector<int>(1, 54), 0);
- addCategory(3, "Audio", MakeVector<int>(1, 61), 0);
- addCategory(5, "Clothing", MakeVector<int>(1, 60), 0);
- addCategory(6, "Collectables", MakeVector<int>(1, 92), 0);
- addCategory(7, "Creatures", MakeVector<int>(2, 83, 65), 0);
- addCategory(8, "Factions", MakeVector<int>(1, 25), 0);
- addCategory(9, "Gameplay", MakeVector<int>(1, 24), 0);
- addCategory(10, "Hair", MakeVector<int>(1, 26), 0);
- addCategory(11, "Items", MakeVector<int>(2, 27, 85), 0);
- addCategory(19, "Weapons", MakeVector<int>(2, 36, 55), 11);
- addCategory(12, "Locations", MakeVector<int>(7, 22, 30, 70, 88, 89, 90, 91), 0);
- addCategory(4, "Cities", MakeVector<int>(1, 53), 12);
- addCategory(20, "Magic", MakeVector<int>(3, 75, 93, 94), 0);
- addCategory(21, "Models & Textures", MakeVector<int>(1, 29), 0);
- addCategory(13, "NPCs", MakeVector<int>(1, 33), 0);
- addCategory(14, "Patches", MakeVector<int>(2, 79, 84), 0);
- addCategory(15, "Quests", MakeVector<int>(1, 35), 0);
- addCategory(16, "Races & Classes", MakeVector<int>(1, 34), 0);
- addCategory(22, "Skills", MakeVector<int>(1, 73), 0);
- addCategory(17, "UI", MakeVector<int>(1, 42), 0);
- addCategory(18, "Visuals", MakeVector<int>(1, 62), 0);
-}
-
-
-int CategoryFactory::getParentID(unsigned int index) const
-{
- if ((index < 0) || (index >= m_Categories.size())) {
- throw MyException(QObject::tr("invalid index %1").arg(index));
- }
-
- return m_Categories[index].m_ParentID;
-}
-
-
-bool CategoryFactory::categoryExists(int id) const
-{
- return m_IDMap.find(id) != m_IDMap.end();
-}
-
-
-bool CategoryFactory::isDecendantOf(int id, int parentID) const
-{
- std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(id);
- if (iter != m_IDMap.end()) {
- unsigned int index = iter->second;
- if (m_Categories[index].m_ParentID == 0) {
- return false;
- } else if (m_Categories[index].m_ParentID == parentID) {
- return true;
- } else {
- return isDecendantOf(m_Categories[index].m_ParentID, parentID);
- }
- } else {
- qWarning("%d is no valid category id", id);
- return false;
- }
-}
-
-
-bool CategoryFactory::hasChildren(unsigned int index) const
-{
- if ((index < 0) || (index >= m_Categories.size())) {
- throw MyException(QObject::tr("invalid index %1").arg(index));
- }
-
- return m_Categories[index].m_HasChildren;
-}
-
-
-QString CategoryFactory::getCategoryName(unsigned int index) const
-{
- if ((index < 0) || (index >= m_Categories.size())) {
- throw MyException(QObject::tr("invalid index %1").arg(index));
- }
-
- return m_Categories[index].m_Name;
-}
-
-
-int CategoryFactory::getCategoryID(unsigned int index) const
-{
- if ((index < 0) || (index >= m_Categories.size())) {
- throw MyException(QObject::tr("invalid index %1").arg(index));
- }
-
- return m_Categories[index].m_ID;
-}
-
-
-int CategoryFactory::getCategoryIndex(int ID) const
-{
- std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(ID);
- if (iter == m_IDMap.end()) {
- throw MyException(QObject::tr("invalid category id %1").arg(ID));
- }
- return iter->second;
-}
-
-
-
-unsigned int CategoryFactory::resolveNexusID(int nexusID) const
-{
- std::map<int, unsigned int>::const_iterator iter = m_NexusMap.find(nexusID);
- if (iter != m_NexusMap.end()) {
- qDebug("nexus category id %d maps to internal %d", nexusID, iter->second);
- return iter->second;
- } else {
- qDebug("nexus category id %d not mapped", nexusID);
- return 0U;
- }
-}
+#include "categories.h" +#include <utility.h> +#include "report.h" +#include <gameinfo.h> +#include <QObject> +#include <QFile> +#include <QDir> +#include <QList> + + +using namespace MOBase; +using namespace MOShared; + + +CategoryFactory* CategoryFactory::s_Instance = NULL; + + +CategoryFactory::CategoryFactory() +{ + reset(); + + QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat")); + + if (!categoryFile.open(QIODevice::ReadOnly)) { + loadDefaultCategories(); + } else { + int lineNum = 0; + while (!categoryFile.atEnd()) { + QByteArray line = categoryFile.readLine(); + ++lineNum; + QList<QByteArray> cells = line.split('|'); + if (cells.count() != 4) { + qCritical("invalid category line %d: %s (%d cells)", + lineNum, line.constData(), cells.count()); + } else { + std::vector<int> nexusIDs; + if (cells[2].length() > 0) { + QList<QByteArray> nexusIDStrings = cells[2].split(','); + for (QList<QByteArray>::iterator iter = nexusIDStrings.begin(); + iter != nexusIDStrings.end(); ++iter) { + bool ok = false; + int temp = iter->toInt(&ok); + if (!ok) { + qCritical("invalid id %s", iter->constData()); + } + nexusIDs.push_back(temp); + } + } + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[3].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + qCritical("invalid category line %d: %s", + lineNum, line.constData()); + } + addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); + } + } + categoryFile.close(); + } + std::sort(m_Categories.begin(), m_Categories.end()); + setParents(); +} + + +CategoryFactory &CategoryFactory::instance() +{ + if (s_Instance == NULL) { + s_Instance = new CategoryFactory; + } + return *s_Instance; +} + + +void CategoryFactory::reset() +{ + m_Categories.clear(); + addCategory(0, "None", MakeVector<int>(2, 28, 87), 0); +} + + +void CategoryFactory::setParents() +{ + for (std::vector<Category>::iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + iter->m_HasChildren = false; + } + + for (std::vector<Category>::const_iterator categoryIter = m_Categories.begin(); + categoryIter != m_Categories.end(); ++categoryIter) { + if (categoryIter->m_ParentID != 0) { + std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(categoryIter->m_ParentID); + if (iter != m_IDMap.end()) { + m_Categories[iter->second].m_HasChildren = true; + } + } + } +} + + +void CategoryFactory::saveCategories() +{ + QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat")); + + if (!categoryFile.open(QIODevice::WriteOnly)) { + reportError(QObject::tr("Failed to save custom categories")); + return; + } + + categoryFile.resize(0); + for (std::vector<Category>::const_iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + if (iter->m_ID == 0) { + continue; + } + QByteArray line; + line.append(QByteArray::number(iter->m_ID)).append("|") + .append(iter->m_Name.toUtf8()).append("|") + .append(VectorJoin(iter->m_NexusIDs, ",")).append("|") + .append(QByteArray::number(iter->m_ParentID)).append("\n"); + categoryFile.write(line); + } + categoryFile.close(); +} + + +void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) +{ + int index = m_Categories.size(); + m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); + for (std::vector<int>::const_iterator iter = nexusIDs.begin(); + iter != nexusIDs.end(); ++iter) { + m_NexusMap[*iter] = index; + } + m_IDMap[id] = index; +} + + +void CategoryFactory::loadDefaultCategories() +{ + // the order here is relevant as it defines the order in which the + // mods appear in the combo box + addCategory(1, "Animations", MakeVector<int>(1, 51), 0); + addCategory(2, "Armour", MakeVector<int>(1, 54), 0); + addCategory(3, "Audio", MakeVector<int>(1, 61), 0); + addCategory(5, "Clothing", MakeVector<int>(1, 60), 0); + addCategory(6, "Collectables", MakeVector<int>(1, 92), 0); + addCategory(7, "Creatures", MakeVector<int>(2, 83, 65), 0); + addCategory(8, "Factions", MakeVector<int>(1, 25), 0); + addCategory(9, "Gameplay", MakeVector<int>(1, 24), 0); + addCategory(10, "Hair", MakeVector<int>(1, 26), 0); + addCategory(11, "Items", MakeVector<int>(2, 27, 85), 0); + addCategory(19, "Weapons", MakeVector<int>(2, 36, 55), 11); + addCategory(12, "Locations", MakeVector<int>(7, 22, 30, 70, 88, 89, 90, 91), 0); + addCategory(4, "Cities", MakeVector<int>(1, 53), 12); + addCategory(20, "Magic", MakeVector<int>(3, 75, 93, 94), 0); + addCategory(21, "Models & Textures", MakeVector<int>(1, 29), 0); + addCategory(13, "NPCs", MakeVector<int>(1, 33), 0); + addCategory(14, "Patches", MakeVector<int>(2, 79, 84), 0); + addCategory(15, "Quests", MakeVector<int>(1, 35), 0); + addCategory(16, "Races & Classes", MakeVector<int>(1, 34), 0); + addCategory(22, "Skills", MakeVector<int>(1, 73), 0); + addCategory(17, "UI", MakeVector<int>(1, 42), 0); + addCategory(18, "Visuals", MakeVector<int>(1, 62), 0); +} + + +int CategoryFactory::getParentID(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_ParentID; +} + + +bool CategoryFactory::categoryExists(int id) const +{ + return m_IDMap.find(id) != m_IDMap.end(); +} + + +bool CategoryFactory::isDecendantOf(int id, int parentID) const +{ + std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(id); + if (iter != m_IDMap.end()) { + unsigned int index = iter->second; + if (m_Categories[index].m_ParentID == 0) { + return false; + } else if (m_Categories[index].m_ParentID == parentID) { + return true; + } else { + return isDecendantOf(m_Categories[index].m_ParentID, parentID); + } + } else { + qWarning("%d is no valid category id", id); + return false; + } +} + + +bool CategoryFactory::hasChildren(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_HasChildren; +} + + +QString CategoryFactory::getCategoryName(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_Name; +} + + +int CategoryFactory::getCategoryID(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_ID; +} + + +int CategoryFactory::getCategoryIndex(int ID) const +{ + std::map<int, unsigned int>::const_iterator iter = m_IDMap.find(ID); + if (iter == m_IDMap.end()) { + throw MyException(QObject::tr("invalid category id %1").arg(ID)); + } + return iter->second; +} + + + +unsigned int CategoryFactory::resolveNexusID(int nexusID) const +{ + std::map<int, unsigned int>::const_iterator iter = m_NexusMap.find(nexusID); + if (iter != m_NexusMap.end()) { + qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); + return iter->second; + } else { + qDebug("nexus category id %d not mapped", nexusID); + return 0U; + } +} diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 8a634dd8..21e1a5aa 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -17,226 +17,226 @@ 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 "categoriesdialog.h"
-#include "ui_categoriesdialog.h"
-#include "categories.h"
-#include "utility.h"
-#include <QItemDelegate>
-#include <QRegExpValidator>
-#include <QLineEdit>
-#include <QMenu>
-
-
-class NewIDValidator : public QIntValidator {
-public:
- NewIDValidator(const std::set<int> &ids)
- : m_UsedIDs(ids) {}
- virtual State validate(QString &input, int &pos) const {
- State intRes = QIntValidator::validate(input, pos);
- if (intRes == Acceptable) {
- bool ok = false;
- int id = input.toInt(&ok);
- if (m_UsedIDs.find(id) != m_UsedIDs.end()) {
- return QValidator::Intermediate;
- }
- }
- return intRes;
- }
-private:
- const std::set<int> &m_UsedIDs;
-};
-
-
-class ExistingIDValidator : public QIntValidator {
-public:
- ExistingIDValidator(const std::set<int> &ids)
- : m_UsedIDs(ids) {}
- virtual State validate(QString &input, int &pos) const {
- State intRes = QIntValidator::validate(input, pos);
- if (intRes == Acceptable) {
- bool ok = false;
- int id = input.toInt(&ok);
- if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) {
- return QValidator::Acceptable;
- } else {
- return QValidator::Intermediate;
- }
- } else {
- return intRes;
- }
- }
-private:
- const std::set<int> &m_UsedIDs;
-};
-
-
-class ValidatingDelegate : public QItemDelegate {
-
-public:
- ValidatingDelegate(QObject *parent, QValidator *validator)
- : QItemDelegate(parent), m_Validator(validator) {}
-
- QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const
- {
- QLineEdit *edit = new QLineEdit(parent);
- edit->setValidator(m_Validator);
- return edit;
- }
- virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
- {
- QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
- int pos = 0;
- if (m_Validator->validate(edit->text(), pos) == QValidator::Acceptable) {
- QItemDelegate::setModelData(editor, model, index);
- }
- }
-private:
- QValidator *m_Validator;
-};
-
-
-CategoriesDialog::CategoriesDialog(QWidget *parent)
- : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog)
-{
- ui->setupUi(this);
- fillTable();
- connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int)));
-}
-
-CategoriesDialog::~CategoriesDialog()
-{
- delete ui;
-}
-
-
-void CategoriesDialog::cellChanged(int row, int)
-{
- int currentID = ui->categoriesTable->item(row, 0)->text().toInt();
- if (currentID > m_HighestID) {
- m_HighestID = currentID;
- }
-}
-
-
-void CategoriesDialog::commitChanges()
-{
- CategoryFactory &categories = CategoryFactory::instance();
- categories.reset();
-
- for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) {
- int index = ui->categoriesTable->verticalHeader()->logicalIndex(i);
- QString nexusIDString = ui->categoriesTable->item(index, 2)->text();
- QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts);
- std::vector<int> nexusIDs;
- for (QStringList::iterator iter = nexusIDStringList.begin();
- iter != nexusIDStringList.end(); ++iter) {
- nexusIDs.push_back(iter->toInt());
- }
-
- categories.addCategory(
- ui->categoriesTable->item(index, 0)->text().toInt(),
- ui->categoriesTable->item(index, 1)->text(),
- nexusIDs,
- ui->categoriesTable->item(index, 3)->text().toInt());
- }
- categories.setParents();
-
- categories.saveCategories();
-}
-
-
-void CategoriesDialog::refreshIDs()
-{
- m_HighestID = 0;
- for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) {
- int id = ui->categoriesTable->item(i, 0)->text().toInt();
- if (id > m_HighestID) {
- m_HighestID = id;
- }
- m_IDs.insert(id);
- }
-}
-
-
-void CategoriesDialog::fillTable()
-{
- CategoryFactory &categories = CategoryFactory::instance();
- QTableWidget *table = ui->categoriesTable;
-
-#if QT_VERSION >= 0x050000
- table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
- table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
- table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
- table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
- table->verticalHeader()->setSectionsMovable(true);
- table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
-#else
- table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed);
- table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
- table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed);
- table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed);
- table->verticalHeader()->setMovable(true);
- table->verticalHeader()->setResizeMode(QHeaderView::Fixed);
-#endif
-
-
- table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs)));
- table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this)));
- table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs)));
-
- int row = 0;
- for (std::vector<CategoryFactory::Category>::const_iterator iter = categories.m_Categories.begin();
- iter != categories.m_Categories.end(); ++iter, ++row) {
- const CategoryFactory::Category &category = *iter;
- if (category.m_ID == 0) {
- --row;
- continue;
- }
- table->insertRow(row);
-// table->setVerticalHeaderItem(row, new QTableWidgetItem(" "));
-
- QScopedPointer<QTableWidgetItem> idItem(new QTableWidgetItem());
- idItem->setData(Qt::DisplayRole, category.m_ID);
-
- QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(category.m_Name));
- QScopedPointer<QTableWidgetItem> nexusIDItem(new QTableWidgetItem(VectorJoin(category.m_NexusIDs, ",")));
- QScopedPointer<QTableWidgetItem> parentIDItem(new QTableWidgetItem());
- parentIDItem->setData(Qt::DisplayRole, category.m_ParentID);
-
- table->setItem(row, 0, idItem.take());
- table->setItem(row, 1, nameItem.take());
- table->setItem(row, 2, nexusIDItem.take());
- table->setItem(row, 3, parentIDItem.take());
- }
-
- refreshIDs();
-}
-
-
-void CategoriesDialog::addCategory_clicked()
-{
- int row = m_ContextRow >= 0 ? m_ContextRow : 0;
- ui->categoriesTable->insertRow(row);
- ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" "));
- ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID)));
- ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new"));
- ui->categoriesTable->setItem(row, 2, new QTableWidgetItem(""));
- ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0"));
-}
-
-
-void CategoriesDialog::removeCategory_clicked()
-{
- ui->categoriesTable->removeRow(m_ContextRow);
-}
-
-
-void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint &pos)
-{
- m_ContextRow = ui->categoriesTable->rowAt(pos.y());
- QMenu menu;
- menu.addAction(tr("Add"), this, SLOT(addCategory_clicked()));
- menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked()));
-
- menu.exec(ui->categoriesTable->mapToGlobal(pos));
-}
+#include "categoriesdialog.h" +#include "ui_categoriesdialog.h" +#include "categories.h" +#include "utility.h" +#include <QItemDelegate> +#include <QRegExpValidator> +#include <QLineEdit> +#include <QMenu> + + +class NewIDValidator : public QIntValidator { +public: + NewIDValidator(const std::set<int> &ids) + : m_UsedIDs(ids) {} + virtual State validate(QString &input, int &pos) const { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if (m_UsedIDs.find(id) != m_UsedIDs.end()) { + return QValidator::Intermediate; + } + } + return intRes; + } +private: + const std::set<int> &m_UsedIDs; +}; + + +class ExistingIDValidator : public QIntValidator { +public: + ExistingIDValidator(const std::set<int> &ids) + : m_UsedIDs(ids) {} + virtual State validate(QString &input, int &pos) const { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) { + return QValidator::Acceptable; + } else { + return QValidator::Intermediate; + } + } else { + return intRes; + } + } +private: + const std::set<int> &m_UsedIDs; +}; + + +class ValidatingDelegate : public QItemDelegate { + +public: + ValidatingDelegate(QObject *parent, QValidator *validator) + : QItemDelegate(parent), m_Validator(validator) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const + { + QLineEdit *edit = new QLineEdit(parent); + edit->setValidator(m_Validator); + return edit; + } + virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + QLineEdit *edit = qobject_cast<QLineEdit*>(editor); + int pos = 0; + if (m_Validator->validate(edit->text(), pos) == QValidator::Acceptable) { + QItemDelegate::setModelData(editor, model, index); + } + } +private: + QValidator *m_Validator; +}; + + +CategoriesDialog::CategoriesDialog(QWidget *parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) +{ + ui->setupUi(this); + fillTable(); + connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); +} + +CategoriesDialog::~CategoriesDialog() +{ + delete ui; +} + + +void CategoriesDialog::cellChanged(int row, int) +{ + int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); + if (currentID > m_HighestID) { + m_HighestID = currentID; + } +} + + +void CategoriesDialog::commitChanges() +{ + CategoryFactory &categories = CategoryFactory::instance(); + categories.reset(); + + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); + QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); + QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts); + std::vector<int> nexusIDs; + for (QStringList::iterator iter = nexusIDStringList.begin(); + iter != nexusIDStringList.end(); ++iter) { + nexusIDs.push_back(iter->toInt()); + } + + categories.addCategory( + ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), + nexusIDs, + ui->categoriesTable->item(index, 3)->text().toInt()); + } + categories.setParents(); + + categories.saveCategories(); +} + + +void CategoriesDialog::refreshIDs() +{ + m_HighestID = 0; + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int id = ui->categoriesTable->item(i, 0)->text().toInt(); + if (id > m_HighestID) { + m_HighestID = id; + } + m_IDs.insert(id); + } +} + + +void CategoriesDialog::fillTable() +{ + CategoryFactory &categories = CategoryFactory::instance(); + QTableWidget *table = ui->categoriesTable; + +#if QT_VERSION >= 0x050000 + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setSectionsMovable(true); + table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); +#else + table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setMovable(true); + table->verticalHeader()->setResizeMode(QHeaderView::Fixed); +#endif + + + table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + + int row = 0; + for (std::vector<CategoryFactory::Category>::const_iterator iter = categories.m_Categories.begin(); + iter != categories.m_Categories.end(); ++iter, ++row) { + const CategoryFactory::Category &category = *iter; + if (category.m_ID == 0) { + --row; + continue; + } + table->insertRow(row); +// table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + + QScopedPointer<QTableWidgetItem> idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, category.m_ID); + + QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer<QTableWidgetItem> nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); + QScopedPointer<QTableWidgetItem> parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, nexusIDItem.take()); + table->setItem(row, 3, parentIDItem.take()); + } + + refreshIDs(); +} + + +void CategoriesDialog::addCategory_clicked() +{ + int row = m_ContextRow >= 0 ? m_ContextRow : 0; + ui->categoriesTable->insertRow(row); + ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID))); + ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); + ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("")); + ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); +} + + +void CategoriesDialog::removeCategory_clicked() +{ + ui->categoriesTable->removeRow(m_ContextRow); +} + + +void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint &pos) +{ + m_ContextRow = ui->categoriesTable->rowAt(pos.y()); + QMenu menu; + menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); + menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); + + menu.exec(ui->categoriesTable->mapToGlobal(pos)); +} diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 34007e77..67851618 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -17,55 +17,55 @@ 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 CATEGORIESDIALOG_H
-#define CATEGORIESDIALOG_H
-
-#include "tutorabledialog.h"
-#include <set>
-
-namespace Ui {
-class CategoriesDialog;
-}
-
-/**
- * @brief Dialog that allows users to configure mod categories
- **/
-class CategoriesDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
-
- explicit CategoriesDialog(QWidget *parent = 0);
- ~CategoriesDialog();
-
- /**
- * @brief store changes here to the global categories store (categories.h)
- *
- **/
- void commitChanges();
-
-private slots:
-
- void on_categoriesTable_customContextMenuRequested(const QPoint &pos);
- void addCategory_clicked();
- void removeCategory_clicked();
- void cellChanged(int row, int column);
-
-private:
-
- void refreshIDs();
- void fillTable();
-
-private:
-
- Ui::CategoriesDialog *ui;
- int m_ContextRow;
-
- int m_HighestID;
- std::set<int> m_IDs;
-
-};
-
-#endif // CATEGORIESDIALOG_H
-
+#ifndef CATEGORIESDIALOG_H +#define CATEGORIESDIALOG_H + +#include "tutorabledialog.h" +#include <set> + +namespace Ui { +class CategoriesDialog; +} + +/** + * @brief Dialog that allows users to configure mod categories + **/ +class CategoriesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + explicit CategoriesDialog(QWidget *parent = 0); + ~CategoriesDialog(); + + /** + * @brief store changes here to the global categories store (categories.h) + * + **/ + void commitChanges(); + +private slots: + + void on_categoriesTable_customContextMenuRequested(const QPoint &pos); + void addCategory_clicked(); + void removeCategory_clicked(); + void cellChanged(int row, int column); + +private: + + void refreshIDs(); + void fillTable(); + +private: + + Ui::CategoriesDialog *ui; + int m_ContextRow; + + int m_HighestID; + std::set<int> m_IDs; + +}; + +#endif // CATEGORIESDIALOG_H + diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 30ea46ae..c6d458de 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -17,87 +17,91 @@ 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 "directoryrefresher.h"
-#include "utility.h"
-#include "report.h"
-#include <gameinfo.h>
-#include <QDir>
-
-
-DirectoryRefresher::DirectoryRefresher()
- : m_DirectoryStructure(NULL)
-{
-}
-
-DirectoryEntry *DirectoryRefresher::getDirectoryStructure()
-{
- QMutexLocker locker(&m_RefreshLock);
- DirectoryEntry *result = m_DirectoryStructure;
- m_DirectoryStructure = NULL;
- return result;
-}
-
-void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods)
-{
- QMutexLocker locker(&m_RefreshLock);
- m_Mods = mods;
-}
-
-
-void DirectoryRefresher::cleanStructure(DirectoryEntry *structure)
-{
- static wchar_t *files[] = { L"meta.ini", L"readme.txt" };
- for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) {
- structure->removeFile(files[i]);
- }
-
- static wchar_t *dirs[] = { L"fomod" };
- for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) {
- structure->removeDir(dirs[i]);
- }
-}
-
-void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory)
-{
- std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory));
-
- directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority);
- QDir dir(directory);
- QFileInfoList bsaFiles = dir.entryInfoList(QStringList("*.bsa"), QDir::Files);
- foreach (QFileInfo file, bsaFiles) {
- directoryStructure->addFromBSA(ToWString(modName), directoryW,
- ToWString(QDir::toNativeSeparators(file.absoluteFilePath())), priority);
- }
-}
-
-void DirectoryRefresher::refresh()
-{
- QMutexLocker locker(&m_RefreshLock);
-
- delete m_DirectoryStructure;
-
- m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0);
-
- // TODO what was the point of having the priority in this tuple? the list is already sorted by priority
- std::vector<std::tuple<QString, QString, int> >::const_iterator iter = m_Mods.begin();
-
- //TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted!
- for (int i = 1; iter != m_Mods.end(); ++iter, ++i) {
- QString modName = std::get<0>(*iter);
- try {
- addModToStructure(m_DirectoryStructure, modName, i, std::get<1>(*iter));
- } catch (const std::exception &e) {
- emit error(tr("failed to read bsa: %1").arg(e.what()));
- }
- emit progress((i * 100) / m_Mods.size() + 1);
- }
-
- std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data";
- m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
-
- emit progress(100);
-
- cleanStructure(m_DirectoryStructure);
-
- emit refreshed();
-}
+#include "directoryrefresher.h" +#include "utility.h" +#include "report.h" +#include <gameinfo.h> +#include <QDir> + + +using namespace MOBase; +using namespace MOShared; + + +DirectoryRefresher::DirectoryRefresher() + : m_DirectoryStructure(NULL) +{ +} + +DirectoryEntry *DirectoryRefresher::getDirectoryStructure() +{ + QMutexLocker locker(&m_RefreshLock); + DirectoryEntry *result = m_DirectoryStructure; + m_DirectoryStructure = NULL; + return result; +} + +void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods) +{ + QMutexLocker locker(&m_RefreshLock); + m_Mods = mods; +} + + +void DirectoryRefresher::cleanStructure(DirectoryEntry *structure) +{ + static wchar_t *files[] = { L"meta.ini", L"readme.txt" }; + for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { + structure->removeFile(files[i]); + } + + static wchar_t *dirs[] = { L"fomod" }; + for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { + structure->removeDir(dirs[i]); + } +} + +void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority); + QDir dir(directory); + QFileInfoList bsaFiles = dir.entryInfoList(QStringList("*.bsa"), QDir::Files); + foreach (QFileInfo file, bsaFiles) { + directoryStructure->addFromBSA(ToWString(modName), directoryW, + ToWString(QDir::toNativeSeparators(file.absoluteFilePath())), priority); + } +} + +void DirectoryRefresher::refresh() +{ + QMutexLocker locker(&m_RefreshLock); + + delete m_DirectoryStructure; + + m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0); + + // TODO what was the point of having the priority in this tuple? the list is already sorted by priority + std::vector<std::tuple<QString, QString, int> >::const_iterator iter = m_Mods.begin(); + + //TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted! + for (int i = 1; iter != m_Mods.end(); ++iter, ++i) { + QString modName = std::get<0>(*iter); + try { + addModToStructure(m_DirectoryStructure, modName, i, std::get<1>(*iter)); + } catch (const std::exception &e) { + emit error(tr("failed to read bsa: %1").arg(e.what())); + } + emit progress((i * 100) / m_Mods.size() + 1); + } + + std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data"; + m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0); + + emit progress(100); + + cleanStructure(m_DirectoryStructure); + + emit refreshed(); +} diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 47259fdd..5b785b2f 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -17,90 +17,90 @@ 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 DIRECTORYREFRESHER_H
-#define DIRECTORYREFRESHER_H
-
-#include <QObject>
-#include <directoryentry.h>
-#include <vector>
-#include <QMutex>
-#include <tuple>
-
-
-/**
- * @brief used to asynchronously generate the virtual view of the combined data directory
- **/
-class DirectoryRefresher : public QObject
-{
-
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- **/
- DirectoryRefresher();
-
- /**
- * @brief retrieve the updated directory structure
- *
- * returns a pointer to the updated directory structure. DirectoryRefresher
- * deletes its own pointer and the caller takes custody of the pointer
- *
- * @return updated directory structure
- **/
- DirectoryEntry *getDirectoryStructure();
-
- /**
- * @brief sets up the mods to be included in the directory structure
- *
- * @param mods list of the mods to include
- **/
- void setMods(const std::vector<std::tuple<QString, QString, int> > &mods);
-
- /**
- * @brief sets up the directory where mods are stored
- * @param modDirectory the mod directory
- * @note this function could be obsoleted easily by storing absolute paths in the parameter to setMods. This is legacy
- */
- void setModDirectory(const QString &modDirectory);
-
- /**
- * @brief remove files from the directory structure that are known to be irrelevant to the game
- * @param the structure to clean
- */
- static void cleanStructure(DirectoryEntry *structure);
-
- /**
- * @brief add files for a mod to the directory structure, including bsas
- * @param modName
- * @param priorityBSA
- * @param directory
- * @param priorityDir
- */
- static void addModToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory);
-
-public slots:
-
- /**
- * @brief generate a directory structure from the mods set earlier
- **/
- void refresh();
-
-signals:
-
- void progress(int progress);
- void error(const QString &error);
- void refreshed();
-
-private:
-
- std::vector<std::tuple<QString, QString, int> > m_Mods;
- DirectoryEntry *m_DirectoryStructure;
- QMutex m_RefreshLock;
-
-};
-
-#endif // DIRECTORYREFRESHER_H
+#ifndef DIRECTORYREFRESHER_H +#define DIRECTORYREFRESHER_H + +#include <QObject> +#include <directoryentry.h> +#include <vector> +#include <QMutex> +#include <tuple> + + +/** + * @brief used to asynchronously generate the virtual view of the combined data directory + **/ +class DirectoryRefresher : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + * + **/ + DirectoryRefresher(); + + /** + * @brief retrieve the updated directory structure + * + * returns a pointer to the updated directory structure. DirectoryRefresher + * deletes its own pointer and the caller takes custody of the pointer + * + * @return updated directory structure + **/ + MOShared::DirectoryEntry *getDirectoryStructure(); + + /** + * @brief sets up the mods to be included in the directory structure + * + * @param mods list of the mods to include + **/ + void setMods(const std::vector<std::tuple<QString, QString, int> > &mods); + + /** + * @brief sets up the directory where mods are stored + * @param modDirectory the mod directory + * @note this function could be obsoleted easily by storing absolute paths in the parameter to setMods. This is legacy + */ + void setModDirectory(const QString &modDirectory); + + /** + * @brief remove files from the directory structure that are known to be irrelevant to the game + * @param the structure to clean + */ + static void cleanStructure(MOShared::DirectoryEntry *structure); + + /** + * @brief add files for a mod to the directory structure, including bsas + * @param modName + * @param priorityBSA + * @param directory + * @param priorityDir + */ + static void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory); + +public slots: + + /** + * @brief generate a directory structure from the mods set earlier + **/ + void refresh(); + +signals: + + void progress(int progress); + void error(const QString &error); + void refreshed(); + +private: + + std::vector<std::tuple<QString, QString, int> > m_Mods; + MOShared::DirectoryEntry *m_DirectoryStructure; + QMutex m_RefreshLock; + +}; + +#endif // DIRECTORYREFRESHER_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 2e756e5d..6f29bbf1 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using QtJson::Json; +using namespace MOBase; // TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads diff --git a/src/dummybsa.cpp b/src/dummybsa.cpp index 8383a6ad..3357dea6 100644 --- a/src/dummybsa.cpp +++ b/src/dummybsa.cpp @@ -17,228 +17,228 @@ 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 "dummybsa.h"
-#include <QFile>
-#include <gameinfo.h>
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
-
-static void writeUlong(unsigned char* buffer, int offset, unsigned long value)
-{
- union {
- unsigned long ulValue;
- unsigned char cValue[4];
- };
- ulValue = value;
- memcpy(buffer + offset, cValue, 4);
-}
-
-static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value)
-{
- union {
- unsigned long long ullValue;
- unsigned char cValue[8];
- };
- ullValue = value;
- memcpy(buffer + offset, cValue, 8);
-}
-/*
-static unsigned long long genHashInt(const char* pos, const char* end)
-{
- unsigned long long hash2 = 0;
- for (; pos < end; ++pos) {
- hash2 *= 0x1003f;
- hash2 += *pos;
- }
- return hash2;
-}
-
-
-static unsigned long long genHash(const char* fileName)
-{
- char fileNameLower[MAX_PATH + 1];
- int i = 0;
- for (; i < MAX_PATH && fileName[i] != '\0'; ++i) {
- fileNameLower[i] = tolower(fileName[i]);
- }
- fileNameLower[i] = '\0';
-
- char* ext = strrchr(fileNameLower, '.');
- if (ext == NULL) {
- ext = fileNameLower + strlen(fileNameLower);
- }
-
- int length = ext - fileNameLower;
-
- unsigned long long hash = 0ULL;
-
- if (length > 0) {
- hash = *(ext - 1) |
- ((length > 2 ? *(ext - 2) : 0) << 8) |
- (length << 16) |
- (fileNameLower[0] << 24);
- }
-
- if (strcmp(ext + 1, "dds") == 0) {
- hash |= 0x8080;
- } // the rest is not supported currently
-
- hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32;
- hash |= genHashInt(ext, ext + strlen(ext)) << 0x32;
-
- return hash;
-}*/
-
-
-static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end)
-{
- unsigned long hash = 0;
- for (; pos < end; ++pos) {
- hash *= 0x1003f;
- hash += *pos;
- }
- return hash;
-}
-
-
-static unsigned long long genHash(const char* fileName)
-{
- char fileNameLower[MAX_PATH + 1];
- int i = 0;
- for (; i < MAX_PATH && fileName[i] != '\0'; ++i) {
- fileNameLower[i] = static_cast<char>(tolower(fileName[i]));
- if (fileNameLower[i] == '\\') {
- fileNameLower[i] = '/';
- }
- }
- fileNameLower[i] = '\0';
-
- unsigned char *fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower);
-
- char* ext = strrchr(fileNameLower, '.');
- if (ext == NULL) {
- ext = fileNameLower + strlen(fileNameLower);
- }
- unsigned char *extU = reinterpret_cast<unsigned char*>(ext);
-
- int length = ext - fileNameLower;
-
- unsigned long long hash = 0ULL;
-
- if (length > 0) {
- hash = *(extU - 1) |
- ((length > 2 ? *(ext - 2) : 0) << 8) |
- (length << 16) |
- (fileNameLowerU[0] << 24);
- }
-
- if (strlen(ext) > 0) {
- if (strcmp(ext + 1, "kf") == 0) {
- hash |= 0x80;
- } else if (strcmp(ext + 1, "nif") == 0) {
- hash |= 0x8000;
- } else if (strcmp(ext + 1, "dds") == 0) {
- hash |= 0x8080;
- } else if (strcmp(ext + 1, "wav") == 0) {
- hash |= 0x80000000;
- }
-
- unsigned long long temp = static_cast<unsigned long long>(genHashInt(
- fileNameLowerU + 1, extU - 2));
- temp += static_cast<unsigned long long>(genHashInt(
- extU, extU + strlen(ext)));
-
- hash |= (temp & 0xFFFFFFFF) << 32;
- }
- return hash;
-}
-
-
-DummyBSA::DummyBSA()
- : m_Version(GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"),
- m_TotalFileNameLength(0)
-{
-}
-
-
-void DummyBSA::writeHeader(QFile& file)
-{
- unsigned char header[] = {
- 'B', 'S', 'A', '\0', // magic string
- 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later
- 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static
- 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later
- 0x01, 0x00, 0x00, 0x00, // folder count
- 0x01, 0x00, 0x00, 0x00, // file count
- 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later
- 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later
- 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later
- };
-
- writeUlong(header, 4, GameInfo::instance().getBSAVersion());
- writeUlong(header, 12, 0x01 | 0x02); // has directories and has files.
- writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name
- writeUlong(header, 28, m_TotalFileNameLength); // single character file name
-
- writeUlong(header, 32, 2); // has dds
-
- file.write(reinterpret_cast<char*>(header), sizeof(header));
-}
-
-
-void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName)
-{
- unsigned char folderRecord[] = {
- 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash
- 0x01, 0x00, 0x00, 0x00, // file count
- 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name
- };
- // we'd usually have to sort folders be the hash value generated here
- writeUlonglong(folderRecord, 0, genHash(folderName.c_str()));
- writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly
-
- file.write(reinterpret_cast<char*>(folderRecord), sizeof(folderRecord));
-}
-
-
-void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName)
-{
- unsigned char fileRecord[] = {
- 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash
- 0xDE, 0xAD, 0xBE, 0xEF, // size
- 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data
- };
-
- // we'd usually have to sort files by the value generated here
- writeUlonglong(fileRecord, 0, genHash(fileName.c_str()));
- writeUlong( fileRecord, 8, 0);
- writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size
-
- file.write(reinterpret_cast<char*>(fileRecord), sizeof(fileRecord));
-}
-
-
-void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName)
-{
- file.write(folderName.c_str(), folderName.length() + 1);
-
- writeFileRecord(file, m_FileName);
-}
-
-
-void DummyBSA::write(const QString& fileName)
-{
- QFile file(fileName);
- file.open(QIODevice::WriteOnly);
-
- m_TotalFileNameLength = m_FileName.length() + 1;
-
- writeHeader(file);
- writeFolderRecord(file, m_FolderName);
- writeFileRecordBlocks(file, m_FolderName);
- file.write(m_FileName.c_str() , m_FileName.length() + 1);
- char fileSize[] = { 0x00, 0x00, 0x00, 0x00 };
- file.write(fileSize, sizeof(fileSize));
- file.close();
-}
+#include "dummybsa.h" +#include <QFile> +#include <gameinfo.h> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + + +static void writeUlong(unsigned char* buffer, int offset, unsigned long value) +{ + union { + unsigned long ulValue; + unsigned char cValue[4]; + }; + ulValue = value; + memcpy(buffer + offset, cValue, 4); +} + +static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value) +{ + union { + unsigned long long ullValue; + unsigned char cValue[8]; + }; + ullValue = value; + memcpy(buffer + offset, cValue, 8); +} +/* +static unsigned long long genHashInt(const char* pos, const char* end) +{ + unsigned long long hash2 = 0; + for (; pos < end; ++pos) { + hash2 *= 0x1003f; + hash2 += *pos; + } + return hash2; +} + + +static unsigned long long genHash(const char* fileName) +{ + char fileNameLower[MAX_PATH + 1]; + int i = 0; + for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { + fileNameLower[i] = tolower(fileName[i]); + } + fileNameLower[i] = '\0'; + + char* ext = strrchr(fileNameLower, '.'); + if (ext == NULL) { + ext = fileNameLower + strlen(fileNameLower); + } + + int length = ext - fileNameLower; + + unsigned long long hash = 0ULL; + + if (length > 0) { + hash = *(ext - 1) | + ((length > 2 ? *(ext - 2) : 0) << 8) | + (length << 16) | + (fileNameLower[0] << 24); + } + + if (strcmp(ext + 1, "dds") == 0) { + hash |= 0x8080; + } // the rest is not supported currently + + hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32; + hash |= genHashInt(ext, ext + strlen(ext)) << 0x32; + + return hash; +}*/ + + +static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end) +{ + unsigned long hash = 0; + for (; pos < end; ++pos) { + hash *= 0x1003f; + hash += *pos; + } + return hash; +} + + +static unsigned long long genHash(const char* fileName) +{ + char fileNameLower[MAX_PATH + 1]; + int i = 0; + for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { + fileNameLower[i] = static_cast<char>(tolower(fileName[i])); + if (fileNameLower[i] == '\\') { + fileNameLower[i] = '/'; + } + } + fileNameLower[i] = '\0'; + + unsigned char *fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower); + + char* ext = strrchr(fileNameLower, '.'); + if (ext == NULL) { + ext = fileNameLower + strlen(fileNameLower); + } + unsigned char *extU = reinterpret_cast<unsigned char*>(ext); + + int length = ext - fileNameLower; + + unsigned long long hash = 0ULL; + + if (length > 0) { + hash = *(extU - 1) | + ((length > 2 ? *(ext - 2) : 0) << 8) | + (length << 16) | + (fileNameLowerU[0] << 24); + } + + if (strlen(ext) > 0) { + if (strcmp(ext + 1, "kf") == 0) { + hash |= 0x80; + } else if (strcmp(ext + 1, "nif") == 0) { + hash |= 0x8000; + } else if (strcmp(ext + 1, "dds") == 0) { + hash |= 0x8080; + } else if (strcmp(ext + 1, "wav") == 0) { + hash |= 0x80000000; + } + + unsigned long long temp = static_cast<unsigned long long>(genHashInt( + fileNameLowerU + 1, extU - 2)); + temp += static_cast<unsigned long long>(genHashInt( + extU, extU + strlen(ext))); + + hash |= (temp & 0xFFFFFFFF) << 32; + } + return hash; +} + + +DummyBSA::DummyBSA() + : m_Version(MOShared::GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"), + m_TotalFileNameLength(0) +{ +} + + +void DummyBSA::writeHeader(QFile& file) +{ + unsigned char header[] = { + 'B', 'S', 'A', '\0', // magic string + 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later + 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static + 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later + 0x01, 0x00, 0x00, 0x00, // folder count + 0x01, 0x00, 0x00, 0x00, // file count + 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later + 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later + 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later + }; + + writeUlong(header, 4, MOShared::GameInfo::instance().getBSAVersion()); + writeUlong(header, 12, 0x01 | 0x02); // has directories and has files. + writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name + writeUlong(header, 28, m_TotalFileNameLength); // single character file name + + writeUlong(header, 32, 2); // has dds + + file.write(reinterpret_cast<char*>(header), sizeof(header)); +} + + +void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName) +{ + unsigned char folderRecord[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash + 0x01, 0x00, 0x00, 0x00, // file count + 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name + }; + // we'd usually have to sort folders be the hash value generated here + writeUlonglong(folderRecord, 0, genHash(folderName.c_str())); + writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly + + file.write(reinterpret_cast<char*>(folderRecord), sizeof(folderRecord)); +} + + +void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName) +{ + unsigned char fileRecord[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash + 0xDE, 0xAD, 0xBE, 0xEF, // size + 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data + }; + + // we'd usually have to sort files by the value generated here + writeUlonglong(fileRecord, 0, genHash(fileName.c_str())); + writeUlong( fileRecord, 8, 0); + writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size + + file.write(reinterpret_cast<char*>(fileRecord), sizeof(fileRecord)); +} + + +void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName) +{ + file.write(folderName.c_str(), folderName.length() + 1); + + writeFileRecord(file, m_FileName); +} + + +void DummyBSA::write(const QString& fileName) +{ + QFile file(fileName); + file.open(QIODevice::WriteOnly); + + m_TotalFileNameLength = m_FileName.length() + 1; + + writeHeader(file); + writeFolderRecord(file, m_FolderName); + writeFileRecordBlocks(file, m_FolderName); + file.write(m_FileName.c_str() , m_FileName.length() + 1); + char fileSize[] = { 0x00, 0x00, 0x00, 0x00 }; + file.write(fileSize, sizeof(fileSize)); + file.close(); +} diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 419d1395..42f29d83 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -23,6 +23,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QMessageBox> +using namespace MOBase; +using namespace MOShared; + EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) : TutorableDialog("EditExecutables", parent), ui(new Ui::EditExecutablesDialog), m_ExecutablesList(executablesList) diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index fad72dfd..da26b177 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -17,70 +17,70 @@ 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 EDITEXECUTABLESDIALOG_H
-#define EDITEXECUTABLESDIALOG_H
-
-#include "tutorabledialog.h"
-#include <QListWidgetItem>
-#include "executableslist.h"
-
-namespace Ui {
- class EditExecutablesDialog;
-}
-
-/**
- * @brief Dialog to manage the list of executables
- **/
-class EditExecutablesDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- * @param executablesList current list of executables
- * @param parent parent widget
- **/
- explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0);
-
- ~EditExecutablesDialog();
-
- /**
- * @brief retrieve the updated list of executables
- *
- * @return updated list of executables
- **/
- ExecutablesList getExecutablesList() const;
-
-private slots:
-
- void on_binaryEdit_textChanged(const QString &arg1);
-
- void on_addButton_clicked();
-
- void on_browseButton_clicked();
-
- void on_removeButton_clicked();
-
- void on_titleEdit_textChanged(const QString &arg1);
-
- void on_executablesListBox_itemClicked(QListWidgetItem *item);
-
- void on_overwriteAppIDBox_toggled(bool checked);
-
- void on_browseDirButton_clicked();
-
-private:
-
- void resetInput();
-
- void refreshExecutablesWidget();
-
-private:
- Ui::EditExecutablesDialog *ui;
- ExecutablesList m_ExecutablesList;
-};
-
-#endif // EDITEXECUTABLESDIALOG_H
+#ifndef EDITEXECUTABLESDIALOG_H +#define EDITEXECUTABLESDIALOG_H + +#include "tutorabledialog.h" +#include <QListWidgetItem> +#include "executableslist.h" + +namespace Ui { + class EditExecutablesDialog; +} + +/** + * @brief Dialog to manage the list of executables + **/ +class EditExecutablesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param executablesList current list of executables + * @param parent parent widget + **/ + explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0); + + ~EditExecutablesDialog(); + + /** + * @brief retrieve the updated list of executables + * + * @return updated list of executables + **/ + ExecutablesList getExecutablesList() const; + +private slots: + + void on_binaryEdit_textChanged(const QString &arg1); + + void on_addButton_clicked(); + + void on_browseButton_clicked(); + + void on_removeButton_clicked(); + + void on_titleEdit_textChanged(const QString &arg1); + + void on_executablesListBox_itemClicked(QListWidgetItem *item); + + void on_overwriteAppIDBox_toggled(bool checked); + + void on_browseDirButton_clicked(); + +private: + + void resetInput(); + + void refreshExecutablesWidget(); + +private: + Ui::EditExecutablesDialog *ui; + ExecutablesList m_ExecutablesList; +}; + +#endif // EDITEXECUTABLESDIALOG_H diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 036671f0..4e39c1a3 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -25,6 +25,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <algorithm> +using namespace MOBase; +using namespace MOShared; + + QDataStream &operator<<(QDataStream &out, const Executable &obj) { out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO diff --git a/src/executableslist.h b/src/executableslist.h index 7016c560..4c9e662a 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -34,7 +34,7 @@ struct Executable { QString m_Title; QFileInfo m_BinaryInfo; QString m_Arguments; - CloseMOStyle m_CloseMO; + MOShared::CloseMOStyle m_CloseMO; QString m_SteamAppID; QString m_WorkingDirectory; @@ -114,7 +114,7 @@ public: * @param arguments arguments to pass to the executable * @param closeMO if true, MO will be closed when the binary is started **/ - void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); + void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); /** * @brief remove the executable with the specified file name. This needs to be an absolute file path @@ -144,7 +144,7 @@ private: Executable *findExe(const QString &title); - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID); + void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID); private: diff --git a/src/fomodinstallerdialog.cpp b/src/fomodinstallerdialog.cpp index bca05567..97476092 100644 --- a/src/fomodinstallerdialog.cpp +++ b/src/fomodinstallerdialog.cpp @@ -31,6 +31,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Shellapi.h> +using namespace MOBase; + + bool ControlsAscending(QAbstractButton *LHS, QAbstractButton *RHS) { return LHS->text() < RHS->text(); diff --git a/src/fomodinstallerdialog.h b/src/fomodinstallerdialog.h index 875146a6..be4e34b2 100644 --- a/src/fomodinstallerdialog.h +++ b/src/fomodinstallerdialog.h @@ -98,7 +98,7 @@ public: * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?) * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer! **/ - DirectoryTree *updateTree(DirectoryTree *tree); + MOBase::DirectoryTree *updateTree(MOBase::DirectoryTree *tree); protected: @@ -170,7 +170,7 @@ private: static PluginType getPluginType(const QString &typeString); static bool byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS); - bool copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor); + bool copyFileIterator(MOBase::DirectoryTree *sourceTree, MOBase::DirectoryTree *destinationTree, FileDescriptor *descriptor); void readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList); void readPluginType(QXmlStreamReader &reader, Plugin &plugin); void readConditionFlags(QXmlStreamReader &reader, Plugin &plugin); @@ -190,10 +190,10 @@ private: bool testVisible(int pageIndex); bool nextPage(); void activateCurrentPage(); - void moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source); - DirectoryTree::Node *findNode(DirectoryTree::Node *node, const QString &path, bool create); - void copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath, - DirectoryTree::Node *destinationTree, const QString &destinationPath); + void moveTree(MOBase::DirectoryTree::Node *target, MOBase::DirectoryTree::Node *source); + MOBase::DirectoryTree::Node *findNode(MOBase::DirectoryTree::Node *node, const QString &path, bool create); + void copyLeaf(MOBase::DirectoryTree::Node *sourceTree, const QString &sourcePath, + MOBase::DirectoryTree::Node *destinationTree, const QString &destinationPath); private: diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp index bb46ee3f..80c46bc8 100644 --- a/src/gameinfoimpl.cpp +++ b/src/gameinfoimpl.cpp @@ -17,34 +17,38 @@ 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 "gameinfoimpl.h"
-#include <gameinfo.h>
-#include <utility.h>
-#include <QDir>
-
-
-GameInfoImpl::GameInfoImpl()
-{
-}
-
-IGameInfo::Type GameInfoImpl::type() const
-{
- switch (GameInfo::instance().getType()) {
- case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION;
- case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3;
- case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV;
- case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM;
- default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType()));
- }
-}
-
-
-QString GameInfoImpl::path() const
-{
- return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
-}
-
-QString GameInfoImpl::binaryName() const
-{
- return ToQString(GameInfo::instance().getBinaryName());
-}
+#include "gameinfoimpl.h" +#include <gameinfo.h> +#include <utility.h> +#include <QDir> + + +using namespace MOBase; +using namespace MOShared; + + +GameInfoImpl::GameInfoImpl() +{ +} + +IGameInfo::Type GameInfoImpl::type() const +{ + switch (GameInfo::instance().getType()) { + case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION; + case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3; + case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; + case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; + default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); + } +} + + +QString GameInfoImpl::path() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); +} + +QString GameInfoImpl::binaryName() const +{ + return ToQString(GameInfo::instance().getBinaryName()); +} diff --git a/src/gameinfoimpl.h b/src/gameinfoimpl.h index abde6cbd..372c4c20 100644 --- a/src/gameinfoimpl.h +++ b/src/gameinfoimpl.h @@ -17,23 +17,23 @@ 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 GAMEINFOIMPL_H
-#define GAMEINFOIMPL_H
-
-
-#include <igameinfo.h>
-#include <QString>
-
-
-class GameInfoImpl : public IGameInfo
-{
-public:
- GameInfoImpl();
-
- virtual Type type() const;
- virtual QString path() const;
- virtual QString binaryName() const;
-
-};
-
-#endif // GAMEINFOIMPL_H
+#ifndef GAMEINFOIMPL_H +#define GAMEINFOIMPL_H + + +#include <igameinfo.h> +#include <QString> + + +class GameInfoImpl : public MOBase::IGameInfo +{ +public: + GameInfoImpl(); + + virtual Type type() const; + virtual QString path() const; + virtual QString binaryName() const; + +}; + +#endif // GAMEINFOIMPL_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index a6f062de..b1b7ff6c 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -51,6 +51,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <boost/assign.hpp> +using namespace MOBase; +using namespace MOShared; + + typedef Archive* (*CreateArchiveType)(); diff --git a/src/installationmanager.h b/src/installationmanager.h index 054f584a..7d864647 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -42,7 +42,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. * All other archives are managed through the manual "InstallDialog" * @todo this may be a good place to support plugins **/ -class InstallationManager : public QObject, public IInstallationManager +class InstallationManager : public QObject, public MOBase::IInstallationManager { Q_OBJECT @@ -82,13 +82,13 @@ public: **/ static QString getErrorString(Archive::Error errorCode); - void installManual(DirectoryTree::Node *baseNode, DirectoryTree *filesTree, bool &hasIniTweaks, QString &modName, int categoryID, const QString &modsDirectory, QString newestVersion, QString version, int modID, bool success, bool manualRequest); + void installManual(MOBase::DirectoryTree::Node *baseNode, MOBase::DirectoryTree *filesTree, bool &hasIniTweaks, QString &modName, int categoryID, const QString &modsDirectory, QString newestVersion, QString version, int modID, bool success, bool manualRequest); /** * @brief register an installer-plugin * @param the installer to register */ - void registerInstaller(IPluginInstaller *installer); + void registerInstaller(MOBase::IPluginInstaller *installer); /** * @return list of file extensions we can install @@ -123,7 +123,7 @@ public: * @param archiveFile path to the archive to install * @return the installation result */ - virtual IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName); + virtual MOBase::IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName); private: @@ -134,22 +134,22 @@ private: void dummyProgressFile(LPCWSTR) {} - DirectoryTree *createFilesTree(); + MOBase::DirectoryTree *createFilesTree(); // remap all files in the archive to the directory structure represented by baseNode // files not present in baseNode are disabled - void mapToArchive(const DirectoryTree::Node *baseNode); + void mapToArchive(const MOBase::DirectoryTree::Node *baseNode); // recursive worker function for mapToArchive - void mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data); + void mapToArchive(const MOBase::DirectoryTree::Node *node, std::wstring path, FileData * const *data); bool unpackPackageTXT(); bool unpackSingleFile(const QString &fileName); - bool isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle); - DirectoryTree::Node *getSimpleArchiveBase(DirectoryTree *dataTree); - bool checkBainPackage(DirectoryTree *dataTree); - bool checkFomodPackage(DirectoryTree *dataTree, QString &offset, bool &xmlInstaller); + bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle); + MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree); + bool checkBainPackage(MOBase::DirectoryTree *dataTree); + bool checkFomodPackage(MOBase::DirectoryTree *dataTree, QString &offset, bool &xmlInstaller); bool checkNMMInstaller(); void fixModName(QString &name); @@ -160,7 +160,7 @@ private: int modID, const QString &version, const QString &newestVersion, int categoryID); bool installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory); - bool installFomodInternal(DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory, + bool installFomodInternal(MOBase::DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory, int modID, const QString &version, const QString &newestVersion, int categoryID, QString &modName, bool nameGuessed, bool &manualRequest); QString generateBackupName(const QString &directoryName); @@ -174,7 +174,7 @@ private slots: private: struct ByPriority { - bool operator()(IPluginInstaller *LHS, IPluginInstaller *RHS) const + bool operator()(MOBase::IPluginInstaller *LHS, MOBase::IPluginInstaller *RHS) const { return LHS->priority() > RHS->priority(); } @@ -191,7 +191,7 @@ private: QWidget *m_ParentWidget; - std::vector<IPluginInstaller*> m_Installers; + std::vector<MOBase::IPluginInstaller*> m_Installers; std::set<QString, CaseInsensitive> m_SupportedExtensions; QString m_NCCPath; diff --git a/src/installdialog.cpp b/src/installdialog.cpp index 0ad7dc60..ac508516 100644 --- a/src/installdialog.cpp +++ b/src/installdialog.cpp @@ -17,289 +17,292 @@ 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 "installdialog.h"
-#include "ui_installdialog.h"
-
-#include "report.h"
-#include "utility.h"
-#include "installationtester.h"
-
-#include <QMenu>
-#include <QInputDialog>
-#include <QMessageBox>
-
-
-InstallDialog::InstallDialog(DirectoryTree *tree, const QString &modName, QWidget *parent)
- : TutorableDialog("InstallDialog", parent), ui(new Ui::InstallDialog),
- m_DataTree(tree), m_TreeRoot(NULL), m_DataRoot(NULL), m_TreeSelection(NULL),
- m_Updating(false)
-{
- ui->setupUi(this);
-
- QLineEdit *editName = findChild<QLineEdit*>("editName");
- editName->setText(modName);
-
- m_Tree = findChild<ArchiveTree*>("treeContent");
-
- m_ProblemLabel = findChild<QLabel*>("problemLabel");
-
- connect(m_Tree, SIGNAL(changed()), this, SLOT(treeChanged()));
-
- updatePreview();
-}
-
-InstallDialog::~InstallDialog()
-{
- delete ui;
-}
-
-
-QString InstallDialog::getModName() const
-{
- QLineEdit *editName = findChild<QLineEdit*>("editName");
- return editName->text();
-}
-
-
-void InstallDialog::mapDataNode(DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const
-{
- for (int i = 0; i < baseItem->childCount(); ++i) {
- QTreeWidgetItem *currentItem = baseItem->child(i);
-
- if (currentItem->checkState(0) != Qt::Unchecked) {
- if (currentItem->data(0, Qt::UserRole).isNull()) {
- DirectoryTree::Node *newNode = new DirectoryTree::Node;
- newNode->setData(currentItem->text(0));
- mapDataNode(newNode, currentItem);
- node->addNode(newNode, true);
- } else {
- node->addLeaf(FileTreeInformation(currentItem->text(0), currentItem->data(0, Qt::UserRole).toInt()));
- }
- }
- }
-}
-
-
-DirectoryTree *InstallDialog::getDataTree() const
-{
- DirectoryTree *base = new DirectoryTree;
-
- mapDataNode(base, m_Tree->topLevelItem(0));
- return base;
-}
-
-
-QString InstallDialog::getFullPath(const DirectoryTree::Node *node)
-{
- QString result(node->getData().name);
- const DirectoryTree::Node *parent = node->getParent();
- while (parent != NULL) {
- if (parent->getParent() != NULL) {
- result.prepend("\\");
- }
- result.prepend(parent->getData().name);
- parent = parent->getParent();
- }
- return result;
-}
-
-
-void InstallDialog::addDataToTree(DirectoryTree::Node *node, QTreeWidgetItem *treeItem)
-{
- QString path = getFullPath(node);
-
- // add directory elements
- for (DirectoryTree::node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
- QStringList fields((*iter)->getData().name);
- QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(treeItem, fields);
- newNodeItem->setFlags(newNodeItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate);
- newNodeItem->setCheckState(0, Qt::Checked);
- addDataToTree(*iter, newNodeItem);
- treeItem->addChild(newNodeItem);
- }
-
- // add file elements
- for (DirectoryTree::leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) {
- QStringList fields(iter->getName());
-
- QTreeWidgetItem *newLeafItem = new QTreeWidgetItem(treeItem, fields);
- newLeafItem->setFlags(newLeafItem->flags() | Qt::ItemIsUserCheckable);
- newLeafItem->setCheckState(0, Qt::Checked);
- if (path.size() != 0) {
- newLeafItem->setToolTip(0, path.mid(0).append("\\").append(iter->getName()));
- } else {
- newLeafItem->setToolTip(0, iter->getName());
- }
- newLeafItem->setData(0, Qt::UserRole, iter->getIndex());
-
- treeItem->addChild(newLeafItem);
- }
-}
-
-
-void InstallDialog::updatePreview()
-{
- m_Updating = true;
- m_Tree->clear();
- delete m_TreeRoot;
-
- m_TreeRoot = new QTreeWidgetItem(m_Tree, QStringList("<data>"));
-
- addDataToTree(m_DataTree, m_TreeRoot);
-
- setDataRoot(m_TreeRoot);
- m_Updating = false;
- updateProblems();
-}
-
-
-bool InstallDialog::testForProblem()
-{
- bool ok = false;
- QTreeWidgetItem *tlWidget = m_Tree->topLevelItem(0);
- for (int i = 0; i < tlWidget->childCount(); ++i) {
- QTreeWidgetItem *widget = tlWidget->child(i);
- if (widget->checkState(0) == Qt::Unchecked) {
- continue;
- }
-
- if (widget->data(0, Qt::UserRole).isValid()) {
- // file
- if (InstallationTester::isTopLevelSuffix(widget->text(0))) {
- ok = true;
- break;
- }
- } else {
- // directory
- if (InstallationTester::isTopLevelDirectory(widget->text(0))) {
- ok = true;
- break;
- }
- }
- }
- return ok;
-}
-
-
-void InstallDialog::updateProblems()
-{
-
- if (testForProblem()) {
- m_ProblemLabel->setText(tr("Looks good"));
- m_ProblemLabel->setToolTip(tr("No problem detected"));
- m_ProblemLabel->setStyleSheet("color: darkGreen;");
- } else {
- m_ProblemLabel->setText(tr("No game data on top level"));
- m_ProblemLabel->setToolTip(tr("There is no esp/esm file or asset directory (textures, meshes, interface, ...) "
- "on the top level."));
- m_ProblemLabel->setStyleSheet("color: red;");
- }
-}
-
-
-void InstallDialog::setDataRoot(QTreeWidgetItem* root)
-{
- if (root != NULL) {
- m_DataRoot = root;
-
- m_Tree->takeTopLevelItem(0);
- QTreeWidgetItem *temp = root->clone();
-// temp->setCheckState(0, Qt::Checked);
- temp->setFlags(temp->flags() & ~(Qt::ItemIsUserCheckable | Qt::ItemIsTristate));
- temp->setText(0, "<data>");
- temp->setData(0, Qt::CheckStateRole, QVariant());
- m_Tree->addTopLevelItem(temp);
- temp->setExpanded(true);
- } else {
- m_Tree->takeTopLevelItem(0);
- }
- updateProblems();
-}
-
-
-void InstallDialog::use_as_data()
-{
- if (m_TreeSelection != NULL) {
- setDataRoot(m_TreeSelection);
- m_TreeSelection = NULL;
- }
- updateProblems();
-}
-
-
-void InstallDialog::unset_data()
-{
- m_TreeSelection = NULL;
-
- setDataRoot(m_TreeRoot);
- updateProblems();
-}
-
-
-void InstallDialog::create_directory()
-{
- bool ok = false;
- QString result = QInputDialog::getText(this, tr("Enter a directory name"), tr("Name"),
- QLineEdit::Normal, QString(), &ok);
- if (ok && !result.isEmpty()) {
- for (int i = 0; i < m_TreeSelection->childCount(); ++i) {
- if (m_TreeSelection->child(i)->text(0) == result) {
- reportError(tr("A directory with that name exists"));
- return;
- }
- }
- QStringList fields(result);
- QTreeWidgetItem *newItem = new QTreeWidgetItem(m_TreeSelection, fields);
- newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable);
- newItem->setCheckState(0, Qt::Checked);
- m_TreeSelection->addChild(newItem);
- updateProblems();
- }
-}
-
-void InstallDialog::open_file()
-{
- emit openFile(m_TreeSelection->toolTip(0));
-}
-
-
-void InstallDialog::on_treeContent_customContextMenuRequested(QPoint pos)
-{
- m_TreeSelection = m_Tree->itemAt(pos);
- if (m_TreeSelection == 0) {
- return;
- }
-
- QMenu menu;
- menu.addAction(tr("Set data directory"), this, SLOT(use_as_data()));
- menu.addAction(tr("Unset data directory"), this, SLOT(unset_data()));
- if (m_TreeSelection->data(0, Qt::UserRole).isNull()) {
- menu.addAction(tr("Create directory..."), this, SLOT(create_directory()));
- } else {
- menu.addAction(tr("&Open"), this, SLOT(open_file()));
- }
- menu.exec(m_Tree->mapToGlobal(pos));
-}
-
-
-void InstallDialog::treeChanged()
-{
- updateProblems();
-}
-
-
-void InstallDialog::on_okButton_clicked()
-{
- if (!testForProblem()) {
- if (QMessageBox::question(this, tr("Continue?"), tr("This mod was probably NOT set up correctly, most likely it will NOT work. Really continue?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
- return;
- }
- }
- this->accept();
-}
-
-void InstallDialog::on_cancelButton_clicked()
-{
- this->reject();
-}
+#include "installdialog.h" +#include "ui_installdialog.h" + +#include "report.h" +#include "utility.h" +#include "installationtester.h" + +#include <QMenu> +#include <QInputDialog> +#include <QMessageBox> + + +using namespace MOBase; + + +InstallDialog::InstallDialog(DirectoryTree *tree, const QString &modName, QWidget *parent) + : TutorableDialog("InstallDialog", parent), ui(new Ui::InstallDialog), + m_DataTree(tree), m_TreeRoot(NULL), m_DataRoot(NULL), m_TreeSelection(NULL), + m_Updating(false) +{ + ui->setupUi(this); + + QLineEdit *editName = findChild<QLineEdit*>("editName"); + editName->setText(modName); + + m_Tree = findChild<ArchiveTree*>("treeContent"); + + m_ProblemLabel = findChild<QLabel*>("problemLabel"); + + connect(m_Tree, SIGNAL(changed()), this, SLOT(treeChanged())); + + updatePreview(); +} + +InstallDialog::~InstallDialog() +{ + delete ui; +} + + +QString InstallDialog::getModName() const +{ + QLineEdit *editName = findChild<QLineEdit*>("editName"); + return editName->text(); +} + + +void InstallDialog::mapDataNode(DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const +{ + for (int i = 0; i < baseItem->childCount(); ++i) { + QTreeWidgetItem *currentItem = baseItem->child(i); + + if (currentItem->checkState(0) != Qt::Unchecked) { + if (currentItem->data(0, Qt::UserRole).isNull()) { + DirectoryTree::Node *newNode = new DirectoryTree::Node; + newNode->setData(currentItem->text(0)); + mapDataNode(newNode, currentItem); + node->addNode(newNode, true); + } else { + node->addLeaf(FileTreeInformation(currentItem->text(0), currentItem->data(0, Qt::UserRole).toInt())); + } + } + } +} + + +DirectoryTree *InstallDialog::getDataTree() const +{ + DirectoryTree *base = new DirectoryTree; + + mapDataNode(base, m_Tree->topLevelItem(0)); + return base; +} + + +QString InstallDialog::getFullPath(const DirectoryTree::Node *node) +{ + QString result(node->getData().name); + const DirectoryTree::Node *parent = node->getParent(); + while (parent != NULL) { + if (parent->getParent() != NULL) { + result.prepend("\\"); + } + result.prepend(parent->getData().name); + parent = parent->getParent(); + } + return result; +} + + +void InstallDialog::addDataToTree(DirectoryTree::Node *node, QTreeWidgetItem *treeItem) +{ + QString path = getFullPath(node); + + // add directory elements + for (DirectoryTree::node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { + QStringList fields((*iter)->getData().name); + QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(treeItem, fields); + newNodeItem->setFlags(newNodeItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate); + newNodeItem->setCheckState(0, Qt::Checked); + addDataToTree(*iter, newNodeItem); + treeItem->addChild(newNodeItem); + } + + // add file elements + for (DirectoryTree::leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { + QStringList fields(iter->getName()); + + QTreeWidgetItem *newLeafItem = new QTreeWidgetItem(treeItem, fields); + newLeafItem->setFlags(newLeafItem->flags() | Qt::ItemIsUserCheckable); + newLeafItem->setCheckState(0, Qt::Checked); + if (path.size() != 0) { + newLeafItem->setToolTip(0, path.mid(0).append("\\").append(iter->getName())); + } else { + newLeafItem->setToolTip(0, iter->getName()); + } + newLeafItem->setData(0, Qt::UserRole, iter->getIndex()); + + treeItem->addChild(newLeafItem); + } +} + + +void InstallDialog::updatePreview() +{ + m_Updating = true; + m_Tree->clear(); + delete m_TreeRoot; + + m_TreeRoot = new QTreeWidgetItem(m_Tree, QStringList("<data>")); + + addDataToTree(m_DataTree, m_TreeRoot); + + setDataRoot(m_TreeRoot); + m_Updating = false; + updateProblems(); +} + + +bool InstallDialog::testForProblem() +{ + bool ok = false; + QTreeWidgetItem *tlWidget = m_Tree->topLevelItem(0); + for (int i = 0; i < tlWidget->childCount(); ++i) { + QTreeWidgetItem *widget = tlWidget->child(i); + if (widget->checkState(0) == Qt::Unchecked) { + continue; + } + + if (widget->data(0, Qt::UserRole).isValid()) { + // file + if (InstallationTester::isTopLevelSuffix(widget->text(0))) { + ok = true; + break; + } + } else { + // directory + if (InstallationTester::isTopLevelDirectory(widget->text(0))) { + ok = true; + break; + } + } + } + return ok; +} + + +void InstallDialog::updateProblems() +{ + + if (testForProblem()) { + m_ProblemLabel->setText(tr("Looks good")); + m_ProblemLabel->setToolTip(tr("No problem detected")); + m_ProblemLabel->setStyleSheet("color: darkGreen;"); + } else { + m_ProblemLabel->setText(tr("No game data on top level")); + m_ProblemLabel->setToolTip(tr("There is no esp/esm file or asset directory (textures, meshes, interface, ...) " + "on the top level.")); + m_ProblemLabel->setStyleSheet("color: red;"); + } +} + + +void InstallDialog::setDataRoot(QTreeWidgetItem* root) +{ + if (root != NULL) { + m_DataRoot = root; + + m_Tree->takeTopLevelItem(0); + QTreeWidgetItem *temp = root->clone(); +// temp->setCheckState(0, Qt::Checked); + temp->setFlags(temp->flags() & ~(Qt::ItemIsUserCheckable | Qt::ItemIsTristate)); + temp->setText(0, "<data>"); + temp->setData(0, Qt::CheckStateRole, QVariant()); + m_Tree->addTopLevelItem(temp); + temp->setExpanded(true); + } else { + m_Tree->takeTopLevelItem(0); + } + updateProblems(); +} + + +void InstallDialog::use_as_data() +{ + if (m_TreeSelection != NULL) { + setDataRoot(m_TreeSelection); + m_TreeSelection = NULL; + } + updateProblems(); +} + + +void InstallDialog::unset_data() +{ + m_TreeSelection = NULL; + + setDataRoot(m_TreeRoot); + updateProblems(); +} + + +void InstallDialog::create_directory() +{ + bool ok = false; + QString result = QInputDialog::getText(this, tr("Enter a directory name"), tr("Name"), + QLineEdit::Normal, QString(), &ok); + if (ok && !result.isEmpty()) { + for (int i = 0; i < m_TreeSelection->childCount(); ++i) { + if (m_TreeSelection->child(i)->text(0) == result) { + reportError(tr("A directory with that name exists")); + return; + } + } + QStringList fields(result); + QTreeWidgetItem *newItem = new QTreeWidgetItem(m_TreeSelection, fields); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + newItem->setCheckState(0, Qt::Checked); + m_TreeSelection->addChild(newItem); + updateProblems(); + } +} + +void InstallDialog::open_file() +{ + emit openFile(m_TreeSelection->toolTip(0)); +} + + +void InstallDialog::on_treeContent_customContextMenuRequested(QPoint pos) +{ + m_TreeSelection = m_Tree->itemAt(pos); + if (m_TreeSelection == 0) { + return; + } + + QMenu menu; + menu.addAction(tr("Set data directory"), this, SLOT(use_as_data())); + menu.addAction(tr("Unset data directory"), this, SLOT(unset_data())); + if (m_TreeSelection->data(0, Qt::UserRole).isNull()) { + menu.addAction(tr("Create directory..."), this, SLOT(create_directory())); + } else { + menu.addAction(tr("&Open"), this, SLOT(open_file())); + } + menu.exec(m_Tree->mapToGlobal(pos)); +} + + +void InstallDialog::treeChanged() +{ + updateProblems(); +} + + +void InstallDialog::on_okButton_clicked() +{ + if (!testForProblem()) { + if (QMessageBox::question(this, tr("Continue?"), tr("This mod was probably NOT set up correctly, most likely it will NOT work. Really continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + return; + } + } + this->accept(); +} + +void InstallDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/installdialog.h b/src/installdialog.h index 14c9d733..e44d7eee 100644 --- a/src/installdialog.h +++ b/src/installdialog.h @@ -17,111 +17,111 @@ 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 INSTALLDIALOG_H
-#define INSTALLDIALOG_H
-
-#include "mytree.h"
-#include "archivetree.h"
-#include "tutorabledialog.h"
-#include <QDialog>
-#include <QUuid>
-#include <QTreeWidgetItem>
-#include <QProgressDialog>
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-#include <archive.h>
-#include <directorytree.h>
-
-
-namespace Ui {
- class InstallDialog;
-}
-
-
-/**
- * a dialog presented to manually define how a mod is to be installed. It provides
- * a tree view of the file contents that can modified directly
- **/
-class InstallDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- /**
- * @brief constructor
- *
- * @param tree tree structure describing the vanilla archive structure. The InstallDialog does NOT take custody of this pointer!
- * @param modName name of the mod. The name can be modified through the dialog
- * @param parent parent widget
- **/
- explicit InstallDialog(DirectoryTree *tree, const QString &modName, QWidget *parent = 0);
- ~InstallDialog();
-
- /**
- * @brief retrieve the (modified) mod name
- *
- * @return updated mod name
- **/
- QString getModName() const;
-
- /**
- * @brief retrieve the user-modified directory structure
- *
- * @return modified data structure. This is a NEW datatree object for which the caller takes custody
- **/
- DirectoryTree *getDataTree() const;
-
-signals:
-
- void openFile(const QString fileName);
-
-private:
-
- void updatePreview();
- bool testForProblem();
- void updateProblems();
-
- void setDataRoot(QTreeWidgetItem* root);
-
- void updateFileList(QTreeWidgetItem *item, QString targetName, FileData* const *fileData, size_t size) const;
-
- void updateCheckState(QTreeWidgetItem *item);
-// void recursiveCheck(QTreeWidgetItem *item);
-// void recursiveUncheck(QTreeWidgetItem *item);
-
- void addDataToTree(DirectoryTree::Node *node, QTreeWidgetItem *treeItem);
-
- void mapDataNode(DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const;
-
- static QString getFullPath(const DirectoryTree::Node *node);
-
-private slots:
-
- void on_treeContent_customContextMenuRequested(QPoint pos);
-
- void unset_data();
- void use_as_data();
- void create_directory();
- void open_file();
-
- void treeChanged();
-
- void on_cancelButton_clicked();
-
- void on_okButton_clicked();
-
-private:
- Ui::InstallDialog *ui;
-
- DirectoryTree *m_DataTree;
-
- ArchiveTree *m_Tree;
- QLabel *m_ProblemLabel;
- QTreeWidgetItem *m_TreeRoot;
- QTreeWidgetItem *m_DataRoot;
- QTreeWidgetItem *m_TreeSelection;
- bool m_Updating;
-
-};
-
-#endif // INSTALLDIALOG_H
+#ifndef INSTALLDIALOG_H +#define INSTALLDIALOG_H + +#include "mytree.h" +#include "archivetree.h" +#include "tutorabledialog.h" +#include <QDialog> +#include <QUuid> +#include <QTreeWidgetItem> +#include <QProgressDialog> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <archive.h> +#include <directorytree.h> + + +namespace Ui { + class InstallDialog; +} + + +/** + * a dialog presented to manually define how a mod is to be installed. It provides + * a tree view of the file contents that can modified directly + **/ +class InstallDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param tree tree structure describing the vanilla archive structure. The InstallDialog does NOT take custody of this pointer! + * @param modName name of the mod. The name can be modified through the dialog + * @param parent parent widget + **/ + explicit InstallDialog(MOBase::DirectoryTree *tree, const QString &modName, QWidget *parent = 0); + ~InstallDialog(); + + /** + * @brief retrieve the (modified) mod name + * + * @return updated mod name + **/ + QString getModName() const; + + /** + * @brief retrieve the user-modified directory structure + * + * @return modified data structure. This is a NEW datatree object for which the caller takes custody + **/ + MOBase::DirectoryTree *getDataTree() const; + +signals: + + void openFile(const QString fileName); + +private: + + void updatePreview(); + bool testForProblem(); + void updateProblems(); + + void setDataRoot(QTreeWidgetItem* root); + + void updateFileList(QTreeWidgetItem *item, QString targetName, FileData* const *fileData, size_t size) const; + + void updateCheckState(QTreeWidgetItem *item); +// void recursiveCheck(QTreeWidgetItem *item); +// void recursiveUncheck(QTreeWidgetItem *item); + + void addDataToTree(MOBase::DirectoryTree::Node *node, QTreeWidgetItem *treeItem); + + void mapDataNode(MOBase::DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const; + + static QString getFullPath(const MOBase::DirectoryTree::Node *node); + +private slots: + + void on_treeContent_customContextMenuRequested(QPoint pos); + + void unset_data(); + void use_as_data(); + void create_directory(); + void open_file(); + + void treeChanged(); + + void on_cancelButton_clicked(); + + void on_okButton_clicked(); + +private: + Ui::InstallDialog *ui; + + MOBase::DirectoryTree *m_DataTree; + + ArchiveTree *m_Tree; + QLabel *m_ProblemLabel; + QTreeWidgetItem *m_TreeRoot; + QTreeWidgetItem *m_DataRoot; + QTreeWidgetItem *m_TreeSelection; + bool m_Updating; + +}; + +#endif // INSTALLDIALOG_H diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index d10eca47..8eed460f 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -17,269 +17,273 @@ 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 "loadmechanism.h"
-#include "utility.h"
-#include "util.h"
-#include <gameinfo.h>
-#include <appconfig.h>
-#include <QFile>
-#include <QFileInfo>
-#include <QDir>
-#include <QString>
-#include <QMessageBox>
-#include <QCryptographicHash>
-
-
-LoadMechanism::LoadMechanism()
- : m_SelectedMechanism(LOAD_MODORGANIZER)
-{
-}
-
-void LoadMechanism::writeHintFile(const QDir &targetDirectory)
-{
- QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt");
- QFile hintFile(hintFilePath);
- if (hintFile.exists()) {
- hintFile.remove();
- }
- if (!hintFile.open(QIODevice::WriteOnly)) {
- throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString()));
- }
- hintFile.write(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str());
- hintFile.close();
-}
-
-
-void LoadMechanism::removeHintFile(QDir &targetDirectory)
-{
- targetDirectory.remove("mo_path.txt");
-}
-
-
-bool LoadMechanism::isDirectLoadingSupported()
-{
- if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) {
- // oblivion can be loaded directly if it's not the steam variant
- QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
- QDir gameDir(gamePath);
- // test if this is the steam version of oblivion
- if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) {
- return false;
- }
- }
-
- // all other games work afaik
- return true;
-}
-
-
-bool LoadMechanism::isScriptExtenderSupported()
-{
- QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
- if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) ||
- (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) {
- return true;
- } else {
- return false;
- }
-}
-
-
-bool LoadMechanism::isProxyDLLSupported()
-{
- QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
- targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
- return QFile(targetPath).exists();
-}
-
-
-bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS)
-{
- QFile fileLHS(fileNameLHS);
- if (!fileLHS.open(QIODevice::ReadOnly)) {
- throw MyException(QObject::tr("%1 not found").arg(fileNameLHS));
- }
- QByteArray dataLHS = fileLHS.readAll();
- QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5);
-
- fileLHS.close();
-
- QFile fileRHS(fileNameRHS);
- if (!fileRHS.open(QIODevice::ReadOnly)) {
- throw MyException(QObject::tr("%1 not found").arg(fileNameRHS));
- }
- QByteArray dataRHS = fileRHS.readAll();
- QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5);
-
- fileRHS.close();
-
- return hashLHS == hashRHS;
-}
-
-
-void LoadMechanism::deactivateScriptExtender()
-{
- try {
- QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
-
- QString pluginsDirPath = gameDirectory;
- pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins");
-
- QDir pluginsDir(pluginsDirPath);
-
- QString hookDLLName = ToQString(AppConfig::hookDLLName());
- if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) {
- // remove dll from SE plugins directory
- if (!pluginsDir.remove(hookDLLName)) {
- throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName)));
- }
- }
-
- removeHintFile(pluginsDir);
- } catch (const std::exception &e) {
- QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what());
- }
-}
-
-
-void LoadMechanism::deactivateProxyDLL()
-{
- try {
- QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
-
- QString targetPath = gameDirectory;
- targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
-
- QFile targetDLL(targetPath);
- if (targetDLL.exists()) {
- QString origFile = gameDirectory.mid(0).append(ToQString(AppConfig::proxyDLLOrig()));
- // determine if a proxy-dll is installed
- // this is a very crude way of making this decision but it should be good enough
- if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) {
- // remove proxy-dll
- if (!targetDLL.remove()) {
- throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString()));
- } else if (!QFile::rename(origFile, targetPath)) {
- throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath));
- }
- }
- }
-
- removeHintFile(QDir(gameDirectory));
- } catch (const std::exception &e) {
- QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what());
- }
-}
-
-
-void LoadMechanism::activateScriptExtender()
-{
- try {
- QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
-
- QString pluginsDirPath = gameDirectory;
- pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins");
-
- QDir pluginsDir(pluginsDirPath);
-
- if (!pluginsDir.exists()) {
- pluginsDir.mkpath(".");
- }
-
- QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName()));
- QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName()));
-
- QFile dllFile(targetPath);
-
- if (dllFile.exists()) {
- // may be outdated
- if (!hashIdentical(targetPath, hookDLLPath)) {
- dllFile.remove();
- }
- }
-
- if (!dllFile.exists()) {
- // install dll to SE plugins
- if (!QFile::copy(hookDLLPath, targetPath)) {
- throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath));
- }
- }
- writeHintFile(pluginsDir);
- } catch (const std::exception &e) {
- QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what());
- }
-}
-
-
-void LoadMechanism::activateProxyDLL()
-{
- try {
- QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
-
- QString targetPath = gameDirectory;
- targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
- QFile targetDLL(targetPath);
- if (!targetDLL.exists()) {
- return;
- }
-
- QString sourcePath = QDir::fromNativeSeparators(
- ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::proxyDLLSource()));
-
- // this is a very crude way of making this decision but it should be good enough
- if (targetDLL.size() < 24576) {
- // determine if a proxy-dll is already installed and if so, if it's the right one
- if (!hashIdentical(targetPath, sourcePath)) {
- // wrong proxy dll, probably outdated. delete and install the new one
- if (!QFile::remove(targetPath)) {
- throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath));
- }
- if (!QFile::copy(sourcePath, targetPath)) {
- throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath));
- }
- } // otherwise the proxy-dll is already the right one
- } else {
- // no proxy dll installed yet. move the original and insert proxy-dll
-
- QString origFile = gameDirectory;
- origFile.append("/").append(ToQString(AppConfig::proxyDLLOrig()));
-
- if (QFile(origFile).exists()) {
- // orig-file exists. this may happen if the steam-api was updated or the user messed with the
- // dlls.
- if (!QFile::remove(origFile)) {
- throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile));
- }
- }
- if (!QFile::rename(targetPath, origFile)) {
- throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile));
- }
- if (!QFile::copy(sourcePath, targetPath)) {
- throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath));
- }
- }
- writeHintFile(QDir(gameDirectory));
- } catch (const std::exception &e) {
- QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what());
- }
-}
-
-
-void LoadMechanism::activate(EMechanism mechanism)
-{
- switch (mechanism) {
- case LOAD_MODORGANIZER: {
- deactivateProxyDLL();
- deactivateScriptExtender();
- } break;
- case LOAD_SCRIPTEXTENDER: {
- deactivateProxyDLL();
- activateScriptExtender();
- } break;
- case LOAD_PROXYDLL: {
- deactivateScriptExtender();
- activateProxyDLL();
- } break;
- }
-}
-
+#include "loadmechanism.h" +#include "utility.h" +#include "util.h" +#include <gameinfo.h> +#include <appconfig.h> +#include <QFile> +#include <QFileInfo> +#include <QDir> +#include <QString> +#include <QMessageBox> +#include <QCryptographicHash> + + +using namespace MOBase; +using namespace MOShared; + + +LoadMechanism::LoadMechanism() + : m_SelectedMechanism(LOAD_MODORGANIZER) +{ +} + +void LoadMechanism::writeHintFile(const QDir &targetDirectory) +{ + QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); + QFile hintFile(hintFilePath); + if (hintFile.exists()) { + hintFile.remove(); + } + if (!hintFile.open(QIODevice::WriteOnly)) { + throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); + } + hintFile.write(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str()); + hintFile.close(); +} + + +void LoadMechanism::removeHintFile(QDir &targetDirectory) +{ + targetDirectory.remove("mo_path.txt"); +} + + +bool LoadMechanism::isDirectLoadingSupported() +{ + if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) { + // oblivion can be loaded directly if it's not the steam variant + QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + QDir gameDir(gamePath); + // test if this is the steam version of oblivion + if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) { + return false; + } + } + + // all other games work afaik + return true; +} + + +bool LoadMechanism::isScriptExtenderSupported() +{ + QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) || + (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) { + return true; + } else { + return false; + } +} + + +bool LoadMechanism::isProxyDLLSupported() +{ + QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + return QFile(targetPath).exists(); +} + + +bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS) +{ + QFile fileLHS(fileNameLHS); + if (!fileLHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameLHS)); + } + QByteArray dataLHS = fileLHS.readAll(); + QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); + + fileLHS.close(); + + QFile fileRHS(fileNameRHS); + if (!fileRHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameRHS)); + } + QByteArray dataRHS = fileRHS.readAll(); + QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); + + fileRHS.close(); + + return hashLHS == hashRHS; +} + + +void LoadMechanism::deactivateScriptExtender() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString pluginsDirPath = gameDirectory; + pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); + + QDir pluginsDir(pluginsDirPath); + + QString hookDLLName = ToQString(AppConfig::hookDLLName()); + if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) { + // remove dll from SE plugins directory + if (!pluginsDir.remove(hookDLLName)) { + throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName))); + } + } + + removeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what()); + } +} + + +void LoadMechanism::deactivateProxyDLL() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString targetPath = gameDirectory; + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + + QFile targetDLL(targetPath); + if (targetDLL.exists()) { + QString origFile = gameDirectory.mid(0).append(ToQString(AppConfig::proxyDLLOrig())); + // determine if a proxy-dll is installed + // this is a very crude way of making this decision but it should be good enough + if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { + // remove proxy-dll + if (!targetDLL.remove()) { + throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); + } else if (!QFile::rename(origFile, targetPath)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); + } + } + } + + removeHintFile(QDir(gameDirectory)); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); + } +} + + +void LoadMechanism::activateScriptExtender() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString pluginsDirPath = gameDirectory; + pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); + + QDir pluginsDir(pluginsDirPath); + + if (!pluginsDir.exists()) { + pluginsDir.mkpath("."); + } + + QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName())); + QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName())); + + QFile dllFile(targetPath); + + if (dllFile.exists()) { + // may be outdated + if (!hashIdentical(targetPath, hookDLLPath)) { + dllFile.remove(); + } + } + + if (!dllFile.exists()) { + // install dll to SE plugins + if (!QFile::copy(hookDLLPath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath)); + } + } + writeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what()); + } +} + + +void LoadMechanism::activateProxyDLL() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString targetPath = gameDirectory; + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + QFile targetDLL(targetPath); + if (!targetDLL.exists()) { + return; + } + + QString sourcePath = QDir::fromNativeSeparators( + ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::proxyDLLSource())); + + // this is a very crude way of making this decision but it should be good enough + if (targetDLL.size() < 24576) { + // determine if a proxy-dll is already installed and if so, if it's the right one + if (!hashIdentical(targetPath, sourcePath)) { + // wrong proxy dll, probably outdated. delete and install the new one + if (!QFile::remove(targetPath)) { + throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); + } + } // otherwise the proxy-dll is already the right one + } else { + // no proxy dll installed yet. move the original and insert proxy-dll + + QString origFile = gameDirectory; + origFile.append("/").append(ToQString(AppConfig::proxyDLLOrig())); + + if (QFile(origFile).exists()) { + // orig-file exists. this may happen if the steam-api was updated or the user messed with the + // dlls. + if (!QFile::remove(origFile)) { + throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); + } + } + if (!QFile::rename(targetPath, origFile)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); + } + } + writeHintFile(QDir(gameDirectory)); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what()); + } +} + + +void LoadMechanism::activate(EMechanism mechanism) +{ + switch (mechanism) { + case LOAD_MODORGANIZER: { + deactivateProxyDLL(); + deactivateScriptExtender(); + } break; + case LOAD_SCRIPTEXTENDER: { + deactivateProxyDLL(); + activateScriptExtender(); + } break; + case LOAD_PROXYDLL: { + deactivateScriptExtender(); + activateProxyDLL(); + } break; + } +} + diff --git a/src/main.cpp b/src/main.cpp index d452c3b8..af6649b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,6 +66,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") +using namespace MOBase; +using namespace MOShared; + + void removeOldLogfiles() { QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"), diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dd217447..5a8211c8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -91,6 +91,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <TlHelp32.h> +using namespace MOBase; +using namespace MOShared; + MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), @@ -2883,7 +2886,7 @@ void MainWindow::fixMods_clicked() selectedItem->setData(Qt::UserRole, temp); } - const SaveGameGamebryo *save = qvariant_cast<SaveGameGamebryo*>(selectedItem->data(Qt::UserRole)); + const SaveGameGamebryo *save = getSaveGame(selectedItem); // collect the list of missing plugins std::map<QString, std::vector<QString> > missingPlugins; diff --git a/src/mainwindow.h b/src/mainwindow.h index 1d6508be..26e4c9dc 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -57,7 +57,7 @@ namespace Ui { class QToolButton; -class MainWindow : public QMainWindow, public IOrganizer +class MainWindow : public QMainWindow, public MOBase::IOrganizer { Q_OBJECT @@ -91,14 +91,14 @@ public: void loadPlugins(); - virtual IGameInfo &gameInfo() const; + virtual MOBase::IGameInfo &gameInfo() const; virtual QString profileName() const; virtual QString profilePath() const; - virtual VersionInfo appVersion() const; - virtual IModInterface *getMod(const QString &name); - virtual IModInterface *createMod(const QString &name); - virtual bool removeMod(IModInterface *mod); - virtual void modDataChanged(IModInterface *mod); + virtual MOBase::VersionInfo appVersion() const; + virtual MOBase::IModInterface *getMod(const QString &name); + virtual MOBase::IModInterface *createMod(const QString &name); + virtual bool removeMod(MOBase::IModInterface *mod); + virtual void modDataChanged(MOBase::IModInterface *mod); virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); @@ -143,8 +143,8 @@ protected: private: void actionToToolButton(QAction *&sourceAction); - bool verifyPlugin(IPlugin *plugin); - void registerPluginTool(IPluginTool *tool); + bool verifyPlugin(MOBase::IPlugin *plugin); + void registerPluginTool(MOBase::IPluginTool *tool); bool registerPlugin(QObject *pluginObj); void updateToolBar(); @@ -163,7 +163,7 @@ private: HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); - void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly); + void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); void refreshDirectoryStructure(); bool refreshProfiles(bool selectProfile = true); void refreshExecutablesList(); @@ -174,7 +174,7 @@ private: void displayModInformation(int row, int tab = 0); void testExtractBSA(int modIndex); - void writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry); + void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); void renameModInList(QFile &modList, const QString &oldName, const QString &newName); @@ -224,7 +224,7 @@ private: Ui::MainWindow *ui; - TutorialControl m_Tutorial; + MOBase::TutorialControl m_Tutorial; QString m_ExeName; @@ -232,7 +232,7 @@ private: QThread m_RefresherThread; DirectoryRefresher m_DirectoryRefresher; - DirectoryEntry *m_DirectoryStructure; + MOShared::DirectoryEntry *m_DirectoryStructure; std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure QProgressBar *m_RefreshProgress; bool m_Refreshing; @@ -281,9 +281,9 @@ private: QTime m_StartTime; SaveGameInfoWidget *m_CurrentSaveView; - IGameInfo *m_GameInfo; + MOBase::IGameInfo *m_GameInfo; - std::vector<IPluginDiagnose*> m_DiagnosisPlugins; + std::vector<MOBase::IPluginDiagnose*> m_DiagnosisPlugins; private slots: diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 517434a7..3493b710 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -378,9 +378,9 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <widget class="LineEditClear" name="modFilterEdit">
+ <widget class="MOBase::LineEditClear" name="modFilterEdit">
<property name="placeholderText">
- <string>Name filter</string>
+ <string>Namefilter</string>
</property>
</widget>
</item>
@@ -908,7 +908,7 @@ p, li { white-space: pre-wrap; } <item>
<layout class="QVBoxLayout" name="downloadLayout">
<item>
- <widget class="LineEditClear" name="downloadFilterEdit">
+ <widget class="MOBase::LineEditClear" name="downloadFilterEdit">
<property name="placeholderText">
<string>Filter</string>
</property>
@@ -1205,7 +1205,7 @@ Right now this has very limited functionality</string> <layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
- <class>LineEditClear</class>
+ <class>MOBase::LineEditClear</class>
<extends>QLineEdit</extends>
<header>lineeditclear.h</header>
</customwidget>
diff --git a/src/moapplication.cpp b/src/moapplication.cpp index aaae77c0..efb0b615 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -17,64 +17,64 @@ 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 "moapplication.h"
-#include "report.h"
-#include "utility.h"
-#include <appconfig.h>
-#include <QFile>
-#include <QStringList>
-
-
-MOApplication::MOApplication(int argc, char **argv)
- : QApplication(argc, argv)
-{
- connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString)));
-}
-
-
-void MOApplication::setStyleFile(const QString &styleName)
-{
- // remove all files from watch
- QStringList currentWatch = m_StyleWatcher.files();
- if (currentWatch.count() != 0) {
- m_StyleWatcher.removePaths(currentWatch);
- }
- // set new stylesheet or clear it
- if (styleName.length() != 0) {
- QString styleSheetName = applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()) + "/" + styleName;
- m_StyleWatcher.addPath(styleSheetName);
- updateStyle(styleSheetName);
- } else {
- setStyleSheet("");
- }
-}
-
-
-bool MOApplication::notify(QObject *receiver, QEvent *event)
-{
- try {
- return QApplication::notify(receiver, event);
- } catch (const std::exception &e) {
- qCritical("uncaught exception in handler (object %s, eventtype %d): %s",
- receiver->objectName().toUtf8().constData(), event->type(), e.what());
- reportError(tr("an error occured: %1").arg(e.what()));
- return false;
- } catch (...) {
- qCritical("uncaught non-std exception in handler (object %s, eventtype %d)",
- receiver->objectName().toUtf8().constData(), event->type());
- reportError(tr("an error occured"));
- return false;
- }
-}
-
-
-void MOApplication::updateStyle(const QString &fileName)
-{
- QFile file(fileName);
- if (file.open(QFile::ReadOnly)) {
- setStyleSheet(file.readAll());
- } else {
- qDebug("no stylesheet");
- }
- file.close();
-}
+#include "moapplication.h" +#include "report.h" +#include "utility.h" +#include <appconfig.h> +#include <QFile> +#include <QStringList> + + +MOApplication::MOApplication(int argc, char **argv) + : QApplication(argc, argv) +{ + connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); +} + + +void MOApplication::setStyleFile(const QString &styleName) +{ + // remove all files from watch + QStringList currentWatch = m_StyleWatcher.files(); + if (currentWatch.count() != 0) { + m_StyleWatcher.removePaths(currentWatch); + } + // set new stylesheet or clear it + if (styleName.length() != 0) { + QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName; + m_StyleWatcher.addPath(styleSheetName); + updateStyle(styleSheetName); + } else { + setStyleSheet(""); + } +} + + +bool MOApplication::notify(QObject *receiver, QEvent *event) +{ + try { + return QApplication::notify(receiver, event); + } catch (const std::exception &e) { + qCritical("uncaught exception in handler (object %s, eventtype %d): %s", + receiver->objectName().toUtf8().constData(), event->type(), e.what()); + reportError(tr("an error occured: %1").arg(e.what())); + return false; + } catch (...) { + qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", + receiver->objectName().toUtf8().constData(), event->type()); + reportError(tr("an error occured")); + return false; + } +} + + +void MOApplication::updateStyle(const QString &fileName) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly)) { + setStyleSheet(file.readAll()); + } else { + qDebug("no stylesheet"); + } + file.close(); +} diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 0a0cde68..b8ef834f 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -38,6 +38,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <sstream> +using namespace MOBase; +using namespace MOShared; + + std::vector<ModInfo::Ptr> ModInfo::s_Collection; std::map<QString, unsigned int> ModInfo::s_ModsByName; std::map<int, unsigned int> ModInfo::s_ModsByModID; diff --git a/src/modinfo.h b/src/modinfo.h index 12ba89fd..a944a6f6 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -43,7 +43,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. * to manage the mod collection * **/ -class ModInfo : public QObject, public IModInterface +class ModInfo : public QObject, public MOBase::IModInterface { Q_OBJECT @@ -101,7 +101,7 @@ public: /** * @brief read the mod directory and Mod ModInfo objects for all subdirectories **/ - static void updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure); + static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure); static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } @@ -174,7 +174,7 @@ public: * @param dir directory to create from * @return pointer to the info-structure of the newly created/added mod */ - static ModInfo::Ptr createFrom(const QDir &dir, DirectoryEntry **directoryStructure); + static ModInfo::Ptr createFrom(const QDir &dir, MOShared::DirectoryEntry **directoryStructure); virtual bool isRegular() const { return false; } @@ -240,7 +240,7 @@ public: * @brief set/change the version of this mod * @param version new version of the mod */ - virtual void setVersion(const VersionInfo &version); + virtual void setVersion(const MOBase::VersionInfo &version); /** * @brief set the newest version of this mod on the nexus @@ -252,7 +252,7 @@ public: * @todo this function should be made obsolete. All queries for mod information should go through * this class so no public function for this change is required **/ - virtual void setNewestVersion(const VersionInfo &version) = 0; + virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; /** * @brief changes/updates the nexus description text @@ -304,14 +304,14 @@ public: /** * @return version object for machine based comparisons **/ - virtual VersionInfo getVersion() const { return m_Version; } + virtual MOBase::VersionInfo getVersion() const { return m_Version; } /** * @brief getter for the newest version number of this mod * * @return newest version of the mod **/ - virtual VersionInfo getNewestVersion() const = 0; + virtual MOBase::VersionInfo getNewestVersion() const = 0; /** * @brief getter for the nexus mod id @@ -454,7 +454,7 @@ protected: int m_PrimaryCategory; std::set<int> m_Categories; - VersionInfo m_Version; + MOBase::VersionInfo m_Version; private: @@ -553,7 +553,7 @@ public: * * @param version the new version to use **/ - void setVersion(const VersionInfo &version); + void setVersion(const MOBase::VersionInfo &version); /** * @brief set the newest version of this mod on the nexus @@ -565,7 +565,7 @@ public: * @todo this function should be made obsolete. All queries for mod information should go through * this class so no public function for this change is required **/ - void setNewestVersion(const VersionInfo &version) { m_NewestVersion = version; } + void setNewestVersion(const MOBase::VersionInfo &version) { m_NewestVersion = version; } /** * @brief changes/updates the nexus description text @@ -612,7 +612,7 @@ public: * * @return newest version of the mod **/ - VersionInfo getNewestVersion() const { return m_NewestVersion; } + MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } /** * @brief getter for the installation file @@ -727,7 +727,7 @@ private: protected: - ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure); + ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); private: @@ -743,13 +743,13 @@ private: int m_NexusID; bool m_MetaInfoChanged; - VersionInfo m_NewestVersion; + MOBase::VersionInfo m_NewestVersion; EEndorsedState m_EndorsedState; NexusBridge m_NexusBridge; - DirectoryEntry **m_DirectoryStructure; + MOShared::DirectoryEntry **m_DirectoryStructure; }; @@ -777,7 +777,7 @@ public: private: - ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure); + ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); }; @@ -797,7 +797,7 @@ public: virtual bool setName(const QString&) { return false; } virtual void setNotes(const QString&) {} virtual void setNexusID(int) {} - virtual void setNewestVersion(const VersionInfo&) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} virtual void setNexusDescription(const QString&) {} virtual void setIsEndorsed(bool) {} virtual bool remove() { return false; } @@ -805,7 +805,7 @@ public: virtual QString name() const { return tr("Overwrite"); } virtual QString notes() const { return ""; } virtual QString absolutePath() const; - virtual VersionInfo getNewestVersion() const { return ""; } + virtual MOBase::VersionInfo getNewestVersion() const { return ""; } virtual QString getInstallationFile() const { return ""; } virtual int getFixedPriority() const { return INT_MAX; } virtual int getNexusID() const { return -1; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 14b40c91..df42cc11 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -42,6 +42,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using QtJson::Json; +using namespace MOBase; +using namespace MOShared; + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 07aa8e84..03cf44ba 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -51,7 +51,7 @@ class QTreeView; * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system **/ -class ModInfoDialog : public TutorableDialog +class ModInfoDialog : public MOBase::TutorableDialog { Q_OBJECT @@ -76,7 +76,7 @@ public: * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, QWidget *parent = 0); + explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, QWidget *parent = 0); ~ModInfoDialog(); /** @@ -209,8 +209,8 @@ private: QTreeWidgetItem *m_ConflictsContextItem; - const DirectoryEntry *m_Directory; - FilesOrigin *m_Origin; + const MOShared::DirectoryEntry *m_Directory; + MOShared::FilesOrigin *m_Origin; QTextCodec *m_UTF8Codec; }; diff --git a/src/modlist.cpp b/src/modlist.cpp index aac913c5..680e0af5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -44,6 +44,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <algorithm> +using namespace MOBase; + + ModList::ModList(QObject *parent) : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), m_FontMetrics(QFont()) diff --git a/src/motddialog.cpp b/src/motddialog.cpp index fbadf5fd..1bf11a7f 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -33,16 +33,16 @@ MotDDialog::MotDDialog(const QString &message, QWidget *parent) } MotDDialog::~MotDDialog() -{
- delete ui;
-}
-
-void MotDDialog::on_okButton_clicked()
+{ + delete ui; +} + +void MotDDialog::on_okButton_clicked() { this->close(); } void MotDDialog::linkClicked(const QUrl &url) { - ::ShellExecuteW(NULL, L"open", ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); + ::ShellExecuteW(NULL, L"open", MOBase::ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); } diff --git a/src/nexusdialog.cpp b/src/nexusdialog.cpp index 7046cdd7..91c0caf8 100644 --- a/src/nexusdialog.cpp +++ b/src/nexusdialog.cpp @@ -33,6 +33,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QWebHistory> +using namespace MOBase; +using namespace MOShared; + NexusDialog::NexusDialog(NXMAccessManager *accessManager, QWidget *parent) : QDialog(parent), ui(new Ui::NexusDialog), m_ModUrlExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/mods/(\\d+)"), Qt::CaseInsensitive), diff --git a/src/nexusdialog.h b/src/nexusdialog.h index fbe99f3d..d7e8ff50 100644 --- a/src/nexusdialog.h +++ b/src/nexusdialog.h @@ -146,7 +146,7 @@ private: Ui::NexusDialog *ui; - TutorialControl m_Tutorial; + MOBase::TutorialControl m_Tutorial; QString m_Url; QRegExp m_ModUrlExp; diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index ad8c529d..016cdeef 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -27,6 +27,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using QtJson::Json; +using namespace MOBase; +using namespace MOShared; + NexusBridge::NexusBridge() { diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 0e6c0eeb..cca84e06 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -17,311 +17,311 @@ 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 NEXUSINTERFACE_H
-#define NEXUSINTERFACE_H
-
-
-#include "nxmaccessmanager.h"
-
-#include "utility.h"
-#include <gameinfo.h>
-#include <versioninfo.h>
-#include <QNetworkReply>
-#include <QNetworkDiskCache>
-#include <QQueue>
-#include <QVariant>
-#include <QTimer>
-#include <list>
-#include <set>
-
-
-class NexusInterface;
-
-
-/**
- * @brief convenience class to make nxm requests easier
- * usually, all objects that started a nxm request will be signaled if one finished.
- * Therefore, the objects need to store the id of the requests they started and then filter
- * the result.
- * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend
- * to handle and only receive the signals the caused
- **/
-class NexusBridge : public QObject
-{
-
- Q_OBJECT
-
-public:
-
- NexusBridge();
-
- /**
- * @brief request description for a mod
- *
- * @param modID id of the mod caller is interested in
- * @param userData user data to be returned with the result
- * @param url the url to request from
- **/
- void requestDescription(int modID, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request a list of the files belonging to a mod
- *
- * @param modID id of the mod caller is interested in
- * @param userData user data to be returned with the result
- * @param url the url to request from
- **/
- void requestFiles(int modID, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request info about a single file of a mod
- *
- * @param modID id of the mod caller is interested in
- * @param fileID id of the file the caller is interested in
- * @param userData user data to be returned with the result
- * @param url the url to request from
- **/
- void requestFileInfo(int modID, int fileID, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request the download url of a file
- *
- * @param modID id of the mod caller is interested in
- * @param fileID id of the file the caller is interested in
- * @param userData user data to be returned with the result
- * @param url the url to request from
- **/
- void requestDownloadURL(int modID, int fileID, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief requestToggleEndorsement
- * @param modID
- * @param userData
- * @param url
- */
- void requestToggleEndorsement(int modID, bool endorse, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
-signals:
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData);
- void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData);
- void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData);
- void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData);
- void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData);
- void nxmRequestFailed(int modID, QVariant userData, const QString &errorMessage);
-
-public slots:
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
- void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
- void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorMessage);
-
-private:
-
- NexusInterface *m_Interface;
- std::set<int> m_RequestIDs;
-
-};
-
-
-/**
- * @brief Makes asynchronous requests to the nexus API
- *
- * This class can be used to make asynchronous requests to the Nexus API.
- * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the
- * recipient has to filter the response by the id returned when making the request
- **/
-class NexusInterface : public QObject
-{
- Q_OBJECT
-
-public:
-
- static NexusInterface *instance();
-
- /**
- * @return the access manager object used to connect to nexus
- **/
- NXMAccessManager *getAccessManager();
-
- /**
- * @brief request description for a mod
- *
- * @param modID id of the mod caller is interested in
- * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- **/
- int requestDescription(int modID, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request nexus descriptions for multiple mods at once
- * @param modIDs a list of ids of mods the caller is interested in
- * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- */
- int requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request a list of the files belonging to a mod
- *
- * @param modID id of the mod caller is interested in
- * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- **/
- int requestFiles(int modID, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request info about a single file of a mod
- *
- * @param modID id of the mod caller is interested in
- * @param fileID id of the file the caller is interested in
- * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- **/
- int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief request the download url of a file
- *
- * @param modID id of the mod caller is interested in
- * @param fileID id of the file the caller is interested in
- * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- **/
- int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @brief toggle endorsement state of the mod
- * @param modID id of the mod
- * @param endorse true if the mod should be endorsed, false for un-endorse
- * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable)
- * @param userData user data to be returned with the result
- * @param url the url to request from
- * @return int an id to identify the request
- */
- int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData,
- const QString &url = ToQString(GameInfo::instance().getNexusInfoUrl()));
-
- /**
- * @param directory the directory to store cache files
- **/
- void setCacheDirectory(const QString &directory);
-
- /**
- * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API
- * @param nmmVersion the version of nmm to impersonate
- **/
- void setNMMVersion(const QString &nmmVersion);
-
-public:
-
- /**
- * @brief guess the mod id from a filename as delivered by Nexus
- * @param fileName name of the file
- * @return the guessed mod id
- * @note this currently doesn't fit well with the remaining interface but this is the best place for the function
- */
- static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query);
-
-signals:
-
- void requestNXMDownload(const QString &url);
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID);
- void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
- void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
- void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorString);
-
-private slots:
-
- void requestFinished();
- void requestError(QNetworkReply::NetworkError error);
- void requestTimeout();
-
- void downloadRequestedNXM(const QString &url);
-
-private:
-
- struct NXMRequestInfo {
- int m_ModID;
- std::vector<int> m_ModIDList;
- int m_FileID;
- QNetworkReply *m_Reply;
- enum Type {
- TYPE_DESCRIPTION,
- TYPE_FILES,
- TYPE_FILEINFO,
- TYPE_DOWNLOADURL,
- TYPE_TOGGLEENDORSEMENT,
- TYPE_GETUPDATES
- } m_Type;
- QVariant m_UserData;
- QTimer *m_Timeout;
- QString m_URL;
- int m_ID;
- int m_Endorse;
-
- NXMRequestInfo(int modID, Type type, QVariant userData, const QString &url)
- : m_ModID(modID), m_FileID(0), m_Reply(NULL), m_Type(type), m_UserData(userData),
- m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {}
- NXMRequestInfo(std::vector<int> modIDList, Type type, QVariant userData, const QString &url)
- : m_ModID(-1), m_ModIDList(modIDList), m_FileID(0), m_Reply(NULL), m_Type(type), m_UserData(userData),
- m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {}
- NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &url)
- : m_ModID(modID), m_FileID(fileID), m_Reply(NULL), m_Type(type), m_UserData(userData),
- m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {}
-
- private:
- static QAtomicInt s_NextID;
- };
-
- static const int MAX_ACTIVE_DOWNLOADS = 2;
-
-private:
-
- NexusInterface();
- void nextRequest();
- void requestFinished(std::list<NXMRequestInfo>::iterator iter);
-
-private:
-
- static NexusInterface *s_Instance;
-
- QNetworkDiskCache *m_DiskCache;
-
- NXMAccessManager *m_AccessManager;
-
- std::list<NXMRequestInfo> m_ActiveRequest;
- QQueue<NXMRequestInfo> m_RequestQueue;
-
- QString m_NMMVersion;
-
-};
-
-#endif // NEXUSINTERFACE_H
+#ifndef NEXUSINTERFACE_H +#define NEXUSINTERFACE_H + + +#include "nxmaccessmanager.h" + +#include "utility.h" +#include <gameinfo.h> +#include <versioninfo.h> +#include <QNetworkReply> +#include <QNetworkDiskCache> +#include <QQueue> +#include <QVariant> +#include <QTimer> +#include <list> +#include <set> + + +class NexusInterface; + + +/** + * @brief convenience class to make nxm requests easier + * usually, all objects that started a nxm request will be signaled if one finished. + * Therefore, the objects need to store the id of the requests they started and then filter + * the result. + * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend + * to handle and only receive the signals the caused + **/ +class NexusBridge : public QObject +{ + + Q_OBJECT + +public: + + NexusBridge(); + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + void requestDescription(int modID, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + void requestFiles(int modID, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request info about a single file of a mod + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + void requestFileInfo(int modID, int fileID, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request the download url of a file + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + void requestDownloadURL(int modID, int fileID, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief requestToggleEndorsement + * @param modID + * @param userData + * @param url + */ + void requestToggleEndorsement(int modID, bool endorse, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + +signals: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData); + void nxmRequestFailed(int modID, QVariant userData, const QString &errorMessage); + +public slots: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorMessage); + +private: + + NexusInterface *m_Interface; + std::set<int> m_RequestIDs; + +}; + + +/** + * @brief Makes asynchronous requests to the nexus API + * + * This class can be used to make asynchronous requests to the Nexus API. + * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the + * recipient has to filter the response by the id returned when making the request + **/ +class NexusInterface : public QObject +{ + Q_OBJECT + +public: + + static NexusInterface *instance(); + + /** + * @return the access manager object used to connect to nexus + **/ + NXMAccessManager *getAccessManager(); + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestDescription(int modID, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request nexus descriptions for multiple mods at once + * @param modIDs a list of ids of mods the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + */ + int requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestFiles(int modID, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request info about a single file of a mod + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request the download url of a file + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief toggle endorsement state of the mod + * @param modID id of the mod + * @param endorse true if the mod should be endorsed, false for un-endorse + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + */ + int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @param directory the directory to store cache files + **/ + void setCacheDirectory(const QString &directory); + + /** + * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API + * @param nmmVersion the version of nmm to impersonate + **/ + void setNMMVersion(const QString &nmmVersion); + +public: + + /** + * @brief guess the mod id from a filename as delivered by Nexus + * @param fileName name of the file + * @return the guessed mod id + * @note this currently doesn't fit well with the remaining interface but this is the best place for the function + */ + static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query); + +signals: + + void requestNXMDownload(const QString &url); + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorString); + +private slots: + + void requestFinished(); + void requestError(QNetworkReply::NetworkError error); + void requestTimeout(); + + void downloadRequestedNXM(const QString &url); + +private: + + struct NXMRequestInfo { + int m_ModID; + std::vector<int> m_ModIDList; + int m_FileID; + QNetworkReply *m_Reply; + enum Type { + TYPE_DESCRIPTION, + TYPE_FILES, + TYPE_FILEINFO, + TYPE_DOWNLOADURL, + TYPE_TOGGLEENDORSEMENT, + TYPE_GETUPDATES + } m_Type; + QVariant m_UserData; + QTimer *m_Timeout; + QString m_URL; + int m_ID; + int m_Endorse; + + NXMRequestInfo(int modID, Type type, QVariant userData, const QString &url) + : m_ModID(modID), m_FileID(0), m_Reply(NULL), m_Type(type), m_UserData(userData), + m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {} + NXMRequestInfo(std::vector<int> modIDList, Type type, QVariant userData, const QString &url) + : m_ModID(-1), m_ModIDList(modIDList), m_FileID(0), m_Reply(NULL), m_Type(type), m_UserData(userData), + m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {} + NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &url) + : m_ModID(modID), m_FileID(fileID), m_Reply(NULL), m_Type(type), m_UserData(userData), + m_Timeout(NULL), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {} + + private: + static QAtomicInt s_NextID; + }; + + static const int MAX_ACTIVE_DOWNLOADS = 2; + +private: + + NexusInterface(); + void nextRequest(); + void requestFinished(std::list<NXMRequestInfo>::iterator iter); + +private: + + static NexusInterface *s_Instance; + + QNetworkDiskCache *m_DiskCache; + + NXMAccessManager *m_AccessManager; + + std::list<NXMRequestInfo> m_ActiveRequest; + QQueue<NXMRequestInfo> m_RequestQueue; + + QString m_NMMVersion; + +}; + +#endif // NEXUSINTERFACE_H diff --git a/src/nexusview.cpp b/src/nexusview.cpp index c8c7f09d..269894c2 100644 --- a/src/nexusview.cpp +++ b/src/nexusview.cpp @@ -28,6 +28,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Shlwapi.h> #include "utility.h" + +using namespace MOBase; + + NexusView::NexusView(QWidget *parent) : QWebView(parent), m_LastSeenModID(0) { diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 55fcf0f7..0f9f0209 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -17,137 +17,141 @@ 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 "nxmaccessmanager.h"
-#include "nxmurl.h"
-#include "report.h"
-#include "utility.h"
-#include "selfupdater.h"
-#include <QMessageBox>
-#include <QNetworkProxy>
-#include <QNetworkRequest>
-#include <QNetworkCookie>
-#include <QNetworkCookieJar>
-#include <gameinfo.h>
-
-
-NXMAccessManager::NXMAccessManager(QObject *parent)
- : QNetworkAccessManager(parent), m_LoginReply(NULL)
-{
-}
-
-
-QNetworkReply *NXMAccessManager::createRequest(
- QNetworkAccessManager::Operation operation, const QNetworkRequest &request,
- QIODevice *device)
-{
- if (request.url().scheme() != "nxm") {
- return QNetworkAccessManager::createRequest(operation, request, device);
- }
- if (operation == GetOperation) {
- emit requestNXMDownload(request.url().toString());
-
- // eat the request, everything else will be done by the download manager
- return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation,
- QNetworkRequest(QUrl()));
- } else if (operation == PostOperation) {
- return QNetworkAccessManager::createRequest(operation, request, device);;
- } else {
- return QNetworkAccessManager::createRequest(operation, request, device);
- }
-}
-
-
-void NXMAccessManager::showCookies()
-{
- QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage())));
- foreach (QNetworkCookie cookie, cookies) {
- qDebug("%s - %s", cookie.name().constData(), cookie.value().constData());
- }
-}
-
-
-bool NXMAccessManager::loggedIn() const
-{
- return hasLoginCookies();
-}
-
-
-void NXMAccessManager::login(const QString &username, const QString &password)
-{
- if (m_LoginReply != NULL) {
- return;
- }
-
- if (hasLoginCookies()) {
- emit loginSuccessful(false);
- return;
- }
-
- m_Username = username;
- m_Password = password;
- pageLogin();
-}
-
-
-void NXMAccessManager::pageLogin()
-{
- QString requestString = QString("http://gatekeeper.nexusmods.com/Sessions/?Login&username=%2&password=%3").arg(m_Username).arg(m_Password);
-
- QNetworkRequest request(requestString);
- m_LoginReply = get(request);
-
- m_LoginTimeout.start();
- connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished()));
- connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError)));
-}
-
-
-void NXMAccessManager::loginTimeout()
-{
- emit loginFailed(tr("timeout"));
- m_LoginReply->deleteLater();
- m_LoginReply = NULL;
- m_LoginTimeout.stop();
- m_Username.clear();
- m_Password.clear();
-}
-
-
-void NXMAccessManager::loginError(QNetworkReply::NetworkError)
-{
- emit loginFailed(m_LoginReply->errorString());
- m_LoginTimeout.stop();
- m_LoginReply->deleteLater();
- m_LoginReply = NULL;
- m_Username.clear();
- m_Password.clear();
-}
-
-
-bool NXMAccessManager::hasLoginCookies() const
-{
- bool sidCookie = false;
- QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage())));
- foreach (QNetworkCookie cookie, cookies) {
- if (cookie.name() == "sid") {
- sidCookie = true;
- }
- }
- return sidCookie;
-}
-
-
-void NXMAccessManager::loginFinished()
-{
- if (hasLoginCookies()) {
- emit loginSuccessful(true);
- } else {
- emit loginFailed(tr("Please check your password"));
- }
-
- m_LoginTimeout.stop();
- m_LoginReply->deleteLater();
- m_LoginReply = NULL;
- m_Username.clear();
- m_Password.clear();
-}
+#include "nxmaccessmanager.h" +#include "nxmurl.h" +#include "report.h" +#include "utility.h" +#include "selfupdater.h" +#include <QMessageBox> +#include <QNetworkProxy> +#include <QNetworkRequest> +#include <QNetworkCookie> +#include <QNetworkCookieJar> +#include <gameinfo.h> + + +using namespace MOBase; +using namespace MOShared; + + +NXMAccessManager::NXMAccessManager(QObject *parent) + : QNetworkAccessManager(parent), m_LoginReply(NULL) +{ +} + + +QNetworkReply *NXMAccessManager::createRequest( + QNetworkAccessManager::Operation operation, const QNetworkRequest &request, + QIODevice *device) +{ + if (request.url().scheme() != "nxm") { + return QNetworkAccessManager::createRequest(operation, request, device); + } + if (operation == GetOperation) { + emit requestNXMDownload(request.url().toString()); + + // eat the request, everything else will be done by the download manager + return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, + QNetworkRequest(QUrl())); + } else if (operation == PostOperation) { + return QNetworkAccessManager::createRequest(operation, request, device);; + } else { + return QNetworkAccessManager::createRequest(operation, request, device); + } +} + + +void NXMAccessManager::showCookies() +{ + QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()))); + foreach (QNetworkCookie cookie, cookies) { + qDebug("%s - %s", cookie.name().constData(), cookie.value().constData()); + } +} + + +bool NXMAccessManager::loggedIn() const +{ + return hasLoginCookies(); +} + + +void NXMAccessManager::login(const QString &username, const QString &password) +{ + if (m_LoginReply != NULL) { + return; + } + + if (hasLoginCookies()) { + emit loginSuccessful(false); + return; + } + + m_Username = username; + m_Password = password; + pageLogin(); +} + + +void NXMAccessManager::pageLogin() +{ + QString requestString = QString("http://gatekeeper.nexusmods.com/Sessions/?Login&username=%2&password=%3").arg(m_Username).arg(m_Password); + + QNetworkRequest request(requestString); + m_LoginReply = get(request); + + m_LoginTimeout.start(); + connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); + connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); +} + + +void NXMAccessManager::loginTimeout() +{ + emit loginFailed(tr("timeout")); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + m_LoginTimeout.stop(); + m_Username.clear(); + m_Password.clear(); +} + + +void NXMAccessManager::loginError(QNetworkReply::NetworkError) +{ + emit loginFailed(m_LoginReply->errorString()); + m_LoginTimeout.stop(); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + m_Username.clear(); + m_Password.clear(); +} + + +bool NXMAccessManager::hasLoginCookies() const +{ + bool sidCookie = false; + QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()))); + foreach (QNetworkCookie cookie, cookies) { + if (cookie.name() == "sid") { + sidCookie = true; + } + } + return sidCookie; +} + + +void NXMAccessManager::loginFinished() +{ + if (hasLoginCookies()) { + emit loginSuccessful(true); + } else { + emit loginFailed(tr("Please check your password")); + } + + m_LoginTimeout.stop(); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + m_Username.clear(); + m_Password.clear(); +} diff --git a/src/nxmurl.cpp b/src/nxmurl.cpp index 53eb369c..003b3c58 100644 --- a/src/nxmurl.cpp +++ b/src/nxmurl.cpp @@ -17,18 +17,18 @@ 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 "nxmurl.h"
-#include <utility.h>
-#include <QRegExp>
-#include <QStringList>
-
-NXMUrl::NXMUrl(const QString &url)
-{
- QRegExp exp("nxm://([a-z]+)/mods/(\\d+)/files/(\\d+)", Qt::CaseInsensitive);
- exp.indexIn(url);
- if (exp.captureCount() != 3) {
- throw MyException(tr("invalid nxm-link: %1").arg(url));
- }
- m_ModId = exp.cap(2).toInt();
- m_FileId = exp.cap(3).toInt();
-}
+#include "nxmurl.h" +#include <utility.h> +#include <QRegExp> +#include <QStringList> + +NXMUrl::NXMUrl(const QString &url) +{ + QRegExp exp("nxm://([a-z]+)/mods/(\\d+)/files/(\\d+)", Qt::CaseInsensitive); + exp.indexIn(url); + if (exp.captureCount() != 3) { + throw MOBase::MyException(tr("invalid nxm-link: %1").arg(url)); + } + m_ModId = exp.cap(2).toInt(); + m_FileId = exp.cap(3).toInt(); +} diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 1af73b7b..99699b52 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -17,234 +17,237 @@ 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 "overwriteinfodialog.h"
-#include "ui_overwriteinfodialog.h"
-#include "report.h"
-#include "utility.h"
-#include <QMessageBox>
-#include <QMenu>
-#include <Shlwapi.h>
-
-
-class MyFileSystemModel : public QFileSystemModel {
-
-public:
- MyFileSystemModel(QObject *parent)
- : QFileSystemModel(parent), m_RegularColumnCount(0) {}
-
- virtual int columnCount(const QModelIndex &parent) const {
- m_RegularColumnCount = QFileSystemModel::columnCount(parent);
-// return m_RegularColumnCount + 1;
- return m_RegularColumnCount;
- }
-
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const {
- if ((orientation == Qt::Horizontal) &&
- (section >= m_RegularColumnCount)) {
- if (role == Qt::DisplayRole) {
- return tr("Overwrites");
- } else {
- return QVariant();
- }
- } else {
- return QFileSystemModel::headerData(section, orientation, role);
- }
- }
-
- virtual QVariant data(const QModelIndex &index, int role) const {
- if (index.column() == m_RegularColumnCount + 0) {
- if (role == Qt::DisplayRole) {
- return tr("not implemented");
- } else {
- return QVariant();
- }
- } else {
- return QFileSystemModel::data(index, role);
- }
- }
-
-private:
- mutable int m_RegularColumnCount;
-};
-
-
-OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent)
- : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL),
- m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL)
-{
- ui->setupUi(this);
-
- QString path = modInfo->absolutePath();
- m_FileSystemModel = new MyFileSystemModel(this);
- m_FileSystemModel->setReadOnly(false);
- m_FileSystemModel->setRootPath(path);
- ui->filesView->setModel(m_FileSystemModel);
- ui->filesView->setRootIndex(m_FileSystemModel->index(path));
- ui->filesView->setColumnWidth(0, 250);
-// ui->filesView->header()->hideSection(3);
-
- m_DeleteAction = new QAction(tr("&Delete"), ui->filesView);
- m_RenameAction = new QAction(tr("&Rename"), ui->filesView);
- m_OpenAction = new QAction(tr("&Open"), ui->filesView);
- m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView);
- QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered()));
- QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered()));
- QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered()));
- QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered()));
-}
-
-OverwriteInfoDialog::~OverwriteInfoDialog()
-{
- delete ui;
-}
-
-
-bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index)
-{
- for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) {
- QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index);
- if (m_FileSystemModel->isDir(childIndex)) {
- if (!recursiveDelete(childIndex)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData());
- return false;
- }
- } else {
- if (!m_FileSystemModel->remove(childIndex)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData());
- return false;
- }
- }
- }
- if (!m_FileSystemModel->remove(index)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData());
- return false;
- }
- return true;
-}
-
-
-void OverwriteInfoDialog::deleteFile(const QModelIndex &index)
-{
-
- bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index)
- : m_FileSystemModel->remove(index);
- if (!res) {
- QString fileName = m_FileSystemModel->fileName(index);
- reportError(tr("Failed to delete \"%1\"").arg(fileName));
- }
-}
-
-
-void OverwriteInfoDialog::deleteTriggered()
-{
- if (m_FileSelection.count() == 0) {
- return;
- } else if (m_FileSelection.count() == 1) {
- QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0));
- if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName),
- QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
- return;
- }
- } else {
- if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"),
- QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
- return;
- }
- }
-
- foreach(QModelIndex index, m_FileSelection) {
- deleteFile(index);
- }
-}
-
-
-void OverwriteInfoDialog::renameTriggered()
-{
- QModelIndex selection = m_FileSelection.at(0);
- QModelIndex index = selection.sibling(selection.row(), 0);
- if (!index.isValid() || m_FileSystemModel->isReadOnly()) {
- return;
- }
-
- ui->filesView->edit(index);
-}
-
-
-void OverwriteInfoDialog::openFile(const QModelIndex &index)
-{
- QString fileName = m_FileSystemModel->filePath(index);
-
- HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW);
- if ((int)res <= 32) {
- qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res);
- }
-}
-
-
-void OverwriteInfoDialog::openTriggered()
-{
- foreach(QModelIndex idx, m_FileSelection) {
- openFile(idx);
- }
-}
-
-void OverwriteInfoDialog::createDirectoryTriggered()
-{
- QModelIndex selection = m_FileSelection.at(0);
-
- QModelIndex index = m_FileSystemModel->isDir(selection) ? selection
- : selection.parent();
- index = index.sibling(index.row(), 0);
-
- QString name = tr("New Folder");
- QString path = m_FileSystemModel->filePath(index).append("/");
-
- QModelIndex existingIndex = m_FileSystemModel->index(path + name);
- int suffix = 1;
- while (existingIndex.isValid()) {
- name = tr("New Folder") + QString::number(suffix++);
- existingIndex = m_FileSystemModel->index(path + name);
- }
-
- QModelIndex newIndex = m_FileSystemModel->mkdir(index, name);
- if (!newIndex.isValid()) {
- reportError(tr("Failed to create \"%1\"").arg(name));
- return;
- }
-
- ui->filesView->setCurrentIndex(newIndex);
- ui->filesView->edit(newIndex);
-}
-
-
-void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos)
-{
- QItemSelectionModel *selectionModel = ui->filesView->selectionModel();
- m_FileSelection = selectionModel->selectedRows(0);
-
-// m_FileSelection = m_FileTree->indexAt(pos);
- QMenu menu(ui->filesView);
-
- menu.addAction(m_NewFolderAction);
-
- bool hasFiles = false;
-
- foreach(QModelIndex idx, m_FileSelection) {
- if (m_FileSystemModel->fileInfo(idx).isFile()) {
- hasFiles = true;
- break;
- }
- }
-
- if (selectionModel->hasSelection()) {
- if (hasFiles) {
- menu.addAction(m_OpenAction);
- }
- menu.addAction(m_RenameAction);
- menu.addAction(m_DeleteAction);
- } else {
- m_FileSelection.clear();
- m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0));
- }
- menu.exec(ui->filesView->mapToGlobal(pos));
-}
+#include "overwriteinfodialog.h" +#include "ui_overwriteinfodialog.h" +#include "report.h" +#include "utility.h" +#include <QMessageBox> +#include <QMenu> +#include <Shlwapi.h> + + +using namespace MOBase; + + +class MyFileSystemModel : public QFileSystemModel { + +public: + MyFileSystemModel(QObject *parent) + : QFileSystemModel(parent), m_RegularColumnCount(0) {} + + virtual int columnCount(const QModelIndex &parent) const { + m_RegularColumnCount = QFileSystemModel::columnCount(parent); +// return m_RegularColumnCount + 1; + return m_RegularColumnCount; + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation == Qt::Horizontal) && + (section >= m_RegularColumnCount)) { + if (role == Qt::DisplayRole) { + return tr("Overwrites"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::headerData(section, orientation, role); + } + } + + virtual QVariant data(const QModelIndex &index, int role) const { + if (index.column() == m_RegularColumnCount + 0) { + if (role == Qt::DisplayRole) { + return tr("not implemented"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::data(index, role); + } + } + +private: + mutable int m_RegularColumnCount; +}; + + +OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) + : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL), + m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL) +{ + ui->setupUi(this); + + QString path = modInfo->absolutePath(); + m_FileSystemModel = new MyFileSystemModel(this); + m_FileSystemModel->setReadOnly(false); + m_FileSystemModel->setRootPath(path); + ui->filesView->setModel(m_FileSystemModel); + ui->filesView->setRootIndex(m_FileSystemModel->index(path)); + ui->filesView->setColumnWidth(0, 250); +// ui->filesView->header()->hideSection(3); + + m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); + m_RenameAction = new QAction(tr("&Rename"), ui->filesView); + m_OpenAction = new QAction(tr("&Open"), ui->filesView); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView); + QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); + QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); +} + +OverwriteInfoDialog::~OverwriteInfoDialog() +{ + delete ui; +} + + +bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; +} + + +void OverwriteInfoDialog::deleteFile(const QModelIndex &index) +{ + + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete \"%1\"").arg(fileName)); + } +} + + +void OverwriteInfoDialog::deleteTriggered() +{ + if (m_FileSelection.count() == 0) { + return; + } else if (m_FileSelection.count() == 1) { + QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + + +void OverwriteInfoDialog::renameTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_FileSystemModel->isReadOnly()) { + return; + } + + ui->filesView->edit(index); +} + + +void OverwriteInfoDialog::openFile(const QModelIndex &index) +{ + QString fileName = m_FileSystemModel->filePath(index); + + HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW); + if ((int)res <= 32) { + qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); + } +} + + +void OverwriteInfoDialog::openTriggered() +{ + foreach(QModelIndex idx, m_FileSelection) { + openFile(idx); + } +} + +void OverwriteInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + + QModelIndex index = m_FileSystemModel->isDir(selection) ? selection + : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_FileSystemModel->filePath(index).append("/"); + + QModelIndex existingIndex = m_FileSystemModel->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_FileSystemModel->index(path + name); + } + + QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->filesView->setCurrentIndex(newIndex); + ui->filesView->edit(newIndex); +} + + +void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + +// m_FileSelection = m_FileTree->indexAt(pos); + QMenu menu(ui->filesView); + + menu.addAction(m_NewFolderAction); + + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); + } + menu.addAction(m_RenameAction); + menu.addAction(m_DeleteAction); + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + menu.exec(ui->filesView->mapToGlobal(pos)); +} diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 72a8563b..486d6f42 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -17,835 +17,838 @@ 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 "pluginlist.h"
-#include "report.h"
-#include "inject.h"
-#include <utility.h>
-#include "settings.h"
-#include <gameinfo.h>
-#include <windows_error.h>
-
-#include <QtDebug>
-#include <QMessageBox>
-#include <QMimeData>
-#include <QCoreApplication>
-#include <QDir>
-#include <QTextCodec>
-#include <QFileInfo>
-#include <QListWidgetItem>
-#include <QString>
-#include <QApplication>
-#include <QKeyEvent>
-#include <QSortFilterProxyModel>
-
-#include <tchar.h>
-#include <ctime>
-#include <algorithm>
-#include <stdexcept>
-
-
-
-bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
- return LHS.m_Name.toUpper() < RHS.m_Name.toUpper();
-}
-
-bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
- if (LHS.m_IsMaster && !RHS.m_IsMaster) {
- return true;
- } else if (!LHS.m_IsMaster && RHS.m_IsMaster) {
- return false;
- } else {
- return LHS.m_Priority < RHS.m_Priority;
- }
-}
-
-bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
- QString lhsExtension = LHS.m_Name.right(3).toLower();
- QString rhsExtension = RHS.m_Name.right(3).toLower();
- if (lhsExtension != rhsExtension) {
- return lhsExtension == "esm";
- }
-
- return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0;
-}
-
-PluginList::PluginList(QObject *parent)
- : QAbstractTableModel(parent), m_Modified(false),
- m_FontMetrics(QFont())
-{
-}
-
-
-QString PluginList::getColumnName(int column)
-{
- switch (column) {
- case COL_NAME: return tr("Name");
- case COL_PRIORITY: return tr("Priority");
- case COL_MODINDEX: return tr("Mod Index");
- default: return tr("unknown");
- }
-}
-
-
-QString PluginList::getColumnToolTip(int column)
-{
- switch (column) {
- case COL_NAME: return tr("Name of your mods");
- case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus "
- "overwrites data from plugins with lower priority.");
- case COL_MODINDEX: return tr("The modindex determins the formids of objects originating from this mods.");
- default: return tr("unknown");
- }
-}
-
-
-void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory,
- const QString &pluginsFile, const QString &loadOrderFile,
- const QString &lockedOrderFile)
-{
- emit layoutAboutToBeChanged();
- m_ESPsByName.clear();
- m_ESPsByPriority.clear();
- m_ESPs.clear();
- std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins();
-
- m_CurrentProfile = profileName;
-
- std::vector<FileEntry*> files = baseDirectory.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry *current = *iter;
- if (current == NULL) {
- continue;
- }
- QString filename = QString::fromUtf16(current->getName().c_str());
- QString extension = filename.right(3).toLower();
-
- if ((extension == "esp") || (extension == "esm")) {
- bool forceEnabled = Settings::instance().forceEnableCoreFiles() &&
- std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end();
-
- bool archive = false;
- FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive));
- m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName())));
- }
- }
-
- if (readLoadOrder(loadOrderFile)) {
- int maxPriority = 0;
- // assign known load orders
- for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
- std::map<QString, int>::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower());
- if (priorityIter != m_ESPLoadOrder.end()) {
- if (priorityIter->second > maxPriority) {
- maxPriority = priorityIter->second;
- }
- espIter->m_Priority = priorityIter->second;
- } else {
- espIter->m_Priority = -1;
- }
- }
-
- ++maxPriority;
-
- // assign maximum priorities for plugins with unknown priority
- for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
- if (espIter->m_Priority == -1) {
- espIter->m_Priority = maxPriority++;
- }
- }
- } else {
- // no load order stored, determine by date
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate);
-
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- m_ESPs[i].m_Priority = i;
- }
- }
-
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority
- // remove gaps from the priorities so we can use them as array indices without overflow
- for (int i = 0; i < static_cast<int>(m_ESPs.size()); ++i) {
- m_ESPs[i].m_Priority = i;
- }
-
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works
-
- updateIndices();
-
- readEnabledFrom(pluginsFile);
-
- readLockedOrderFrom(lockedOrderFile);
-
- refreshLoadOrder();
-
- emit layoutChanged();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1));
-}
-
-
-void PluginList::enableESP(const QString &name)
-{
- std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
-
- if (iter != m_ESPsByName.end()) {
- m_ESPs[iter->second].m_Enabled = true;
- m_Modified = true;
- } else {
- reportError(tr("esp not found: %1").arg(name));
- }
-}
-
-
-void PluginList::enableAll()
-{
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- iter->m_Enabled = true;
- }
- m_Modified = true;
- emit esplist_changed();
-}
-
-
-void PluginList::disableAll()
-{
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (!iter->m_ForceEnabled) {
- iter->m_Enabled = false;
- }
- }
- m_Modified = true;
- emit esplist_changed();
-}
-
-
-bool PluginList::isEnabled(const QString &name)
-{
- std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
-
- if (iter != m_ESPsByName.end()) {
- return m_ESPs[iter->second].m_Enabled;
- } else {
- return false;
- }
-}
-
-
-bool PluginList::isEnabled(int index)
-{
- return m_ESPs.at(index).m_Enabled;
-}
-
-
-bool PluginList::readLoadOrder(const QString &fileName)
-{
- std::set<QString> availableESPs;
- for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- availableESPs.insert(iter->m_Name.toLower());
- }
-
- m_ESPLoadOrder.clear();
-
- int priority = 0;
-
- std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins();
- for (std::vector<std::wstring>::iterator iter = primaryPlugins.begin();
- iter != primaryPlugins.end(); ++iter) {
- if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) {
- m_ESPLoadOrder[ToQString(*iter)] = priority++;
- }
- }
-
- QFile file(fileName);
- if (!file.open(QIODevice::ReadOnly)) {
- return false;
- }
- while (!file.atEnd()) {
- QByteArray line = file.readLine().trimmed();
- QString modName;
- if ((line.size() > 0) && (line.at(0) != '#')) {
- modName = QString::fromUtf8(line.constData()).toLower();
- }
-
- if ((modName.size() > 0) &&
- (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) &&
- (availableESPs.find(modName) != availableESPs.end())) {
- m_ESPLoadOrder[modName] = priority++;
- }
- }
-
- file.close();
- return true;
-}
-
-
-void PluginList::readEnabledFrom(const QString &fileName)
-{
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (!iter->m_ForceEnabled) {
- iter->m_Enabled = false;
- }
- iter->m_LoadOrder = -1;
- }
-
- QFile file(fileName);
- if (!file.exists()) {
- throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData());
- }
-
- file.open(QIODevice::ReadOnly);
- while (!file.atEnd()) {
- QByteArray line = file.readLine();
- QString modName;
- if ((line.size() > 0) && (line.at(0) != '#')) {
- modName = QString::fromUtf8(line.trimmed().constData());
- }
- if (modName.size() > 0) {
- std::map<QString, int>::iterator iter = m_ESPsByName.find(modName.toLower());
- if (iter != m_ESPsByName.end()) {
- m_ESPs[iter->second].m_Enabled = true;
- } else {
- qWarning("plugin %s not found", modName.toUtf8().constData());
- m_Modified = true;
- }
- }
- }
-
- file.close();
-}
-
-
-void PluginList::readLockedOrderFrom(const QString &fileName)
-{
- m_LockedOrder.clear();
-
- QFile file(fileName);
- if (!file.exists()) {
- // no locked load order, that's ok
- return;
- }
-
- file.open(QIODevice::ReadOnly);
- while (!file.atEnd()) {
- QByteArray line = file.readLine();
- if ((line.size() > 0) && (line.at(0) != '#')) {
- QList<QByteArray> fields = line.split('|');
- if (fields.count() == 2) {
- m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt();
- } else {
- reportError(tr("The file containing locked plugin indices is broken"));
- break;
- }
- }
- }
-
- file.close();
-}
-
-
-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));
- }
-
- QTextCodec *textCodec = writeUnchecked ? QTextCodec::codecForName("utf-8")
- : QTextCodec::codecForName("Windows-1252");
-
- if (textCodec == NULL) {
- QList<QByteArray> encodingList = QTextCodec::availableCodecs();
- QString encodings;
- QTextStream temp(&encodings);
- foreach (QByteArray encoding, encodingList) {
- temp << encoding << ", ";
- }
- qCritical("required string-encoding not supported. Available codecs: %s",
- encodings.toUtf8().constData());
-
- throw std::runtime_error(QObject::tr("encoding error, please report this as a bug and include the file "
- "mo_interface.log!").toUtf8().constData());
- }
-
- file.resize(0);
-
- file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n"));
-
- bool invalidFileNames = false;
-
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- int priority = m_ESPsByPriority[i];
- if ((m_ESPs[priority].m_Enabled || writeUnchecked) && !m_ESPs[priority].m_Removed) {
- //file.write(m_ESPs[priority].m_Name.toUtf8());
- if (!textCodec->canEncode(m_ESPs[priority].m_Name)) {
- 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("\r\n");
- }
- }
- 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."));
- }
-
- 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));
- }
-
- 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.close();
-}
-
-
-void PluginList::saveTo(const QString &pluginFileName, const QString &loadOrderFileName, const QString &lockedOrderFileName, const QString& deleterFileName, bool hideUnchecked) const
-{
- if (!m_Modified) {
- return;
- }
-
- writePlugins(pluginFileName, false);
- writePlugins(loadOrderFileName, true);
- writeLockedOrder(lockedOrderFileName);
-
- if (hideUnchecked) {
- QFile deleterFile(deleterFileName);
- deleterFile.open(QIODevice::WriteOnly);
- deleterFile.resize(0);
- deleterFile.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8());
-
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- int priority = m_ESPsByPriority[i];
- if (m_ESPs[priority].m_Removed) {
- deleterFile.write(m_ESPs[priority].m_Name.toUtf8());
- deleterFile.write("\r\n");
- }
- }
- deleterFile.close();
- qDebug("%s saved", QDir::toNativeSeparators(deleterFileName).toUtf8().constData());
- }
-
- m_Modified = false;
-}
-
-
-bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
-{
- if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) {
- // nothing to do
- return true;
- }
-
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- std::wstring espName = ToWString(iter->m_Name);
- const FileEntry *fileEntry = directoryStructure.findFile(espName);
- if (fileEntry != NULL) {
- QString fileName;
- bool archive = false;
- int originid = fileEntry->getOrigin(archive);
- fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name);
-
- HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE,
- 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if (file == INVALID_HANDLE_VALUE) {
- if (::GetLastError() == ERROR_SHARING_VIOLATION) {
- // file is locked, probably the game is running
- return false;
- } else {
- throw windows_error(QObject::tr("failed to access %1").arg(fileName).toUtf8().constData());
- }
- }
-
- ULONGLONG temp = 0;
- temp = (145731ULL + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL;
-
- FILETIME newWriteTime;
-
- newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
- newWriteTime.dwHighDateTime = (DWORD)(temp >> 32);
- iter->m_Time = newWriteTime;
- fileEntry->setFileTime(newWriteTime);
- if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) {
- throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData());
- }
-
- CloseHandle(file);
- }
- }
- return true;
-}
-
-bool PluginList::isESPLocked(int index) const
-{
- return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end();
-}
-
-
-void PluginList::lockESPIndex(int index, bool lock)
-{
- if (lock) {
- m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder;
- } else {
- auto iter = m_LockedOrder.find(getName(index).toLower());
- if (iter != m_LockedOrder.end()) {
- m_LockedOrder.erase(iter);
- }
- }
- m_Modified = true;
- emit esplist_changed();
-}
-
-
-void PluginList::syncLoadOrder()
-{
- int loadOrder = 0;
- for (unsigned int i = 0; i < m_ESPs.size(); ++i) {
- int index = m_ESPsByPriority[i];
-
- if (m_ESPs[index].m_Enabled) {
- m_ESPs[index].m_LoadOrder = loadOrder++;
- } else {
- m_ESPs[index].m_LoadOrder = -1;
- }
- }
-}
-
-void PluginList::refreshLoadOrder()
-{
- syncLoadOrder();
-
- // set priorities according to locked load order
- std::map<int, QString> lockedLoadOrder;
- std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(),
- [&lockedLoadOrder] (const std::pair<QString, int> &ele) { lockedLoadOrder[ele.second] = ele.first; });
-
- int targetPrio = 0;
- // this is guaranteed to iterate from lowest key (load order) to highest
- for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) {
- auto nameIter = m_ESPsByName.find(iter->second);
- if (nameIter != m_ESPsByName.end()) {
- // locked esp exists
-
- // find the location to insert at
- while ((targetPrio < static_cast<int>(m_ESPs.size())) &&
- (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) {
- if (QString::compare(m_ESPs[m_ESPsByPriority[targetPrio]].m_Name, iter->second) != 0) {
- ++targetPrio;
- }
- }
- int temp = targetPrio;
- if (m_ESPs[nameIter->second].m_Priority != temp) {
- setPluginPriority(nameIter->second, temp);
- m_ESPs[nameIter->second].m_LoadOrder = iter->first;
- syncLoadOrder();
- m_Modified = true;
- }
- }
- }
-}
-
-
-void PluginList::updateIndices()
-{
- m_ESPsByName.clear();
- m_ESPsByPriority.clear();
- m_ESPsByPriority.resize(m_ESPs.size());
-
- for (unsigned int i = 0; i < m_ESPs.size(); ++i) {
- m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
- m_ESPsByPriority[m_ESPs[i].m_Priority] = i;
- }
-}
-
-
-int PluginList::rowCount(const QModelIndex &parent) const
-{
- if (!parent.isValid()) {
- return m_ESPs.size();
- } else {
- return 0;
- }
-}
-
-int PluginList::columnCount(const QModelIndex &) const
-{
- return 3;
-}
-
-
-QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
-{
- int index = modelIndex.row();
-
- if (role == Qt::DisplayRole) {
- switch (modelIndex.column()) {
- case COL_NAME: {
- return m_ESPs[index].m_Name;
- } break;
- case COL_PRIORITY: {
- if (m_ESPs[index].m_Priority == 0) {
- return tr("min");
- } else if (m_ESPs[index].m_Priority == m_ESPs.size() - 1) {
- return tr("max");
- } else {
- return QString::number(m_ESPs[index].m_Priority);
- }
- } break;
- case COL_MODINDEX: {
- if (m_ESPs[index].m_LoadOrder == -1) {
- return QString();
- } else {
- return QString("%1").arg(m_ESPs[index].m_LoadOrder, 2, 16, QChar('0')).toUpper();
- }
- } break;
- default: {
- return QVariant();
- } break;
- }
- } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) {
- if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) {
- return QIcon(":/MO/gui/locked");
- } 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::TextAlignmentRole) {
- if (modelIndex.column() == 0) {
- return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
- } else {
- return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
- }
- } else if (role == Qt::ToolTipRole) {
- if (m_ESPs[index].m_ForceEnabled) {
- return tr("This plugin can't be disabled (enforced by the game)");
- } else {
- return tr("Origin: %1").arg(m_ESPs[index].m_OriginName);
- }
- } else {
- return QVariant();
- }
-}
-
-
-bool PluginList::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (role == Qt::CheckStateRole) {
- m_ESPs[index.row()].m_Enabled = value.toInt() == Qt::Checked;
-
- emit layoutAboutToBeChanged();
-
- refreshLoadOrder();
- m_Modified = true;
- emit esplist_changed();
-
- emit layoutChanged();
- return true;
- } else {
- return false;
- }
-}
-
-
-QVariant PluginList::headerData(int section, Qt::Orientation orientation,
- int role) const
-{
- if (orientation == Qt::Horizontal) {
- if (role == Qt::DisplayRole) {
- return getColumnName(section);
- } else if (role == Qt::ToolTipRole) {
- return getColumnToolTip(section);
- } else if (role == Qt::SizeHintRole) {
- QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section));
- temp.rwidth() += 25;
- temp.rheight() += 12;
- return temp;
- }
- }
- return QAbstractItemModel::headerData(section, orientation, role);
-}
-
-
-Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const
-{
- int index = modelIndex.row();
- Qt::ItemFlags result = QAbstractTableModel::flags(modelIndex);
-
- if (modelIndex.isValid()) {
- if ((m_ESPs[index].m_ForceEnabled)) {
- result &= ~Qt::ItemIsEnabled;
- }
- result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
- } else {
- result |= Qt::ItemIsDropEnabled;
- }
-
- return result;
-}
-
-
-void PluginList::setPluginPriority(int row, int &newPriority)
-{
- int newPriorityTemp = newPriority;
-
- QString sourceExtension = m_ESPs[row].m_Name.right(3).toLower();
- if (sourceExtension == "esp") {
- // don't allow esps to be moved above esms
- while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
- (m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_Name.right(3).toLower() == "esm")) {
- ++newPriorityTemp;
- }
- } else {
- // don't allow esms to be moved below esps
- while ((newPriorityTemp > 0) &&
- (m_ESPs[m_ESPsByPriority[newPriorityTemp - 1]].m_Name.right(3).toLower() == "esp")) {
- --newPriorityTemp;
- }
- // also don't allow "regular" esms to be moved above primary plugins
- while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
- (m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_ForceEnabled)) {
- ++newPriorityTemp;
- }
- }
-
- int oldPriority = m_ESPs[row].m_Priority;
- if (newPriorityTemp > oldPriority) {
- // priority is higher than the old, so the gap we left is in lower priorities
- for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) {
- --m_ESPs[m_ESPsByPriority[i]].m_Priority;
- }
- } else {
- for (int i = newPriorityTemp; i < oldPriority; ++i) {
- ++m_ESPs[m_ESPsByPriority[i]].m_Priority;
- }
- ++newPriority;
- }
-
- m_ESPs[row].m_Priority = newPriorityTemp;
- updateIndices();
-}
-
-
-void PluginList::changePluginPriority(std::vector<int> rows, int newPriority)
-{
- emit layoutAboutToBeChanged();
- // sort rows to insert by their old priority (ascending) and insert them move them in that order
- const std::vector<ESPInfo> &esp = m_ESPs;
- std::sort(rows.begin(), rows.end(),
- [&esp](const int &LHS, const int &RHS) {
- return esp[LHS].m_Priority < esp[RHS].m_Priority;
- });
-
- // odd stuff: if any of the dragged sources has priority lower than the destination then the
- // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why?
- for (std::vector<int>::const_iterator iter = rows.begin();
- iter != rows.end(); ++iter) {
- if (m_ESPs[*iter].m_Priority < newPriority) {
- --newPriority;
- break;
- }
- }
-
- for (std::vector<int>::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) {
- setPluginPriority(*iter, newPriority);
- }
- refreshLoadOrder();
-
- emit esplist_changed();
-
- m_Modified = true;
-
- emit layoutChanged();
-}
-
-
-bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
-{
- if (action == Qt::IgnoreAction) {
- return true;
- }
-
- QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
- QDataStream stream(&encoded, QIODevice::ReadOnly);
-
- std::vector<int> sourceRows;
-
- while (!stream.atEnd()) {
- int sourceRow, col;
- QMap<int, QVariant> roleDataMap;
- stream >> sourceRow >> col >> roleDataMap;
- if (col == 0) { // only add each row once
- sourceRows.push_back(sourceRow);
- }
- }
-
- if (row == -1) {
- row = parent.row();
- }
-
- int newPriority = 0;
-
- if ((row < 0) ||
- (row >= static_cast<int>(m_ESPs.size()))) {
- newPriority = m_ESPs.size();
- } else {
- newPriority = m_ESPs[row].m_Priority;
- }
- changePluginPriority(sourceRows, newPriority);
-
-
- return false;
-}
-
-
-bool PluginList::eventFilter(QObject *obj, QEvent *event)
-{
- if (event->type() == QEvent::KeyPress) {
- QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
- QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
- if ((itemView != NULL) &&
- (keyEvent->modifiers() == Qt::ControlModifier) &&
- ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
- QItemSelectionModel *selectionModel = itemView->selectionModel();
- const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model());
- int diff = -1;
- if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) ||
- ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) {
- diff = 1;
- }
- QModelIndexList rows = selectionModel->selectedRows();
- if (keyEvent->key() == Qt::Key_Down) {
- for (int i = 0; i < rows.size() / 2; ++i) {
- rows.swap(i, rows.size() - i - 1);
- }
- }
- foreach (QModelIndex idx, rows) {
- if (proxyModel != NULL) {
- idx = proxyModel->mapToSource(idx);
- }
- int newPriority = m_ESPs[idx.row()].m_Priority + diff;
- if ((newPriority >= 0) && (newPriority < rowCount())) {
- setPluginPriority(idx.row(), newPriority);
- emit dataChanged(this->index(idx.row(), 0), this->index(idx.row(), this->columnCount() - 1));
- }
- }
- refreshLoadOrder();
- return true;
- }
- }
- return QObject::eventFilter(obj, event);
-}
+#include "pluginlist.h" +#include "report.h" +#include "inject.h" +#include <utility.h> +#include "settings.h" +#include <gameinfo.h> +#include <windows_error.h> + +#include <QtDebug> +#include <QMessageBox> +#include <QMimeData> +#include <QCoreApplication> +#include <QDir> +#include <QTextCodec> +#include <QFileInfo> +#include <QListWidgetItem> +#include <QString> +#include <QApplication> +#include <QKeyEvent> +#include <QSortFilterProxyModel> + +#include <tchar.h> +#include <ctime> +#include <algorithm> +#include <stdexcept> + + +using namespace MOBase; +using namespace MOShared; + + +bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); +} + +bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + if (LHS.m_IsMaster && !RHS.m_IsMaster) { + return true; + } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { + return false; + } else { + return LHS.m_Priority < RHS.m_Priority; + } +} + +bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + QString lhsExtension = LHS.m_Name.right(3).toLower(); + QString rhsExtension = RHS.m_Name.right(3).toLower(); + if (lhsExtension != rhsExtension) { + return lhsExtension == "esm"; + } + + return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0; +} + +PluginList::PluginList(QObject *parent) + : QAbstractTableModel(parent), m_Modified(false), + m_FontMetrics(QFont()) +{ +} + + +QString PluginList::getColumnName(int column) +{ + switch (column) { + case COL_NAME: return tr("Name"); + case COL_PRIORITY: return tr("Priority"); + case COL_MODINDEX: return tr("Mod Index"); + default: return tr("unknown"); + } +} + + +QString PluginList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: return tr("Name of your mods"); + case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " + "overwrites data from plugins with lower priority."); + case COL_MODINDEX: return tr("The modindex determins the formids of objects originating from this mods."); + default: return tr("unknown"); + } +} + + +void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory, + const QString &pluginsFile, const QString &loadOrderFile, + const QString &lockedOrderFile) +{ + emit layoutAboutToBeChanged(); + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + m_ESPs.clear(); + std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + + m_CurrentProfile = profileName; + + std::vector<FileEntry*> files = baseDirectory.getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + FileEntry *current = *iter; + if (current == NULL) { + continue; + } + QString filename = QString::fromUtf16(current->getName().c_str()); + QString extension = filename.right(3).toLower(); + + if ((extension == "esp") || (extension == "esm")) { + bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end(); + + bool archive = false; + FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); + m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()))); + } + } + + if (readLoadOrder(loadOrderFile)) { + int maxPriority = 0; + // assign known load orders + for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { + std::map<QString, int>::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower()); + if (priorityIter != m_ESPLoadOrder.end()) { + if (priorityIter->second > maxPriority) { + maxPriority = priorityIter->second; + } + espIter->m_Priority = priorityIter->second; + } else { + espIter->m_Priority = -1; + } + } + + ++maxPriority; + + // assign maximum priorities for plugins with unknown priority + for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { + if (espIter->m_Priority == -1) { + espIter->m_Priority = maxPriority++; + } + } + } else { + // no load order stored, determine by date + std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate); + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + m_ESPs[i].m_Priority = i; + } + } + + std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority + // remove gaps from the priorities so we can use them as array indices without overflow + for (int i = 0; i < static_cast<int>(m_ESPs.size()); ++i) { + m_ESPs[i].m_Priority = i; + } + + std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works + + updateIndices(); + + readEnabledFrom(pluginsFile); + + readLockedOrderFrom(lockedOrderFile); + + refreshLoadOrder(); + + emit layoutChanged(); + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1)); +} + + +void PluginList::enableESP(const QString &name) +{ + std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = true; + m_Modified = true; + } else { + reportError(tr("esp not found: %1").arg(name)); + } +} + + +void PluginList::enableAll() +{ + for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + iter->m_Enabled = true; + } + m_Modified = true; + emit esplist_changed(); +} + + +void PluginList::disableAll() +{ + for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + if (!iter->m_ForceEnabled) { + iter->m_Enabled = false; + } + } + m_Modified = true; + emit esplist_changed(); +} + + +bool PluginList::isEnabled(const QString &name) +{ + std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + return m_ESPs[iter->second].m_Enabled; + } else { + return false; + } +} + + +bool PluginList::isEnabled(int index) +{ + return m_ESPs.at(index).m_Enabled; +} + + +bool PluginList::readLoadOrder(const QString &fileName) +{ + std::set<QString> availableESPs; + for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + availableESPs.insert(iter->m_Name.toLower()); + } + + m_ESPLoadOrder.clear(); + + int priority = 0; + + std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + for (std::vector<std::wstring>::iterator iter = primaryPlugins.begin(); + iter != primaryPlugins.end(); ++iter) { + if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) { + m_ESPLoadOrder[ToQString(*iter)] = priority++; + } + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + QString modName; + if ((line.size() > 0) && (line.at(0) != '#')) { + modName = QString::fromUtf8(line.constData()).toLower(); + } + + if ((modName.size() > 0) && + (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) && + (availableESPs.find(modName) != availableESPs.end())) { + m_ESPLoadOrder[modName] = priority++; + } + } + + file.close(); + return true; +} + + +void PluginList::readEnabledFrom(const QString &fileName) +{ + for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + if (!iter->m_ForceEnabled) { + iter->m_Enabled = false; + } + iter->m_LoadOrder = -1; + } + + QFile file(fileName); + if (!file.exists()) { + throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData()); + } + + file.open(QIODevice::ReadOnly); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + QString modName; + if ((line.size() > 0) && (line.at(0) != '#')) { + modName = QString::fromUtf8(line.trimmed().constData()); + } + if (modName.size() > 0) { + std::map<QString, int>::iterator iter = m_ESPsByName.find(modName.toLower()); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = true; + } else { + qWarning("plugin %s not found", modName.toUtf8().constData()); + m_Modified = true; + } + } + } + + file.close(); +} + + +void PluginList::readLockedOrderFrom(const QString &fileName) +{ + m_LockedOrder.clear(); + + QFile file(fileName); + if (!file.exists()) { + // no locked load order, that's ok + return; + } + + file.open(QIODevice::ReadOnly); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + if ((line.size() > 0) && (line.at(0) != '#')) { + QList<QByteArray> fields = line.split('|'); + if (fields.count() == 2) { + m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt(); + } else { + reportError(tr("The file containing locked plugin indices is broken")); + break; + } + } + } + + file.close(); +} + + +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)); + } + + QTextCodec *textCodec = writeUnchecked ? QTextCodec::codecForName("utf-8") + : QTextCodec::codecForName("Windows-1252"); + + if (textCodec == NULL) { + QList<QByteArray> encodingList = QTextCodec::availableCodecs(); + QString encodings; + QTextStream temp(&encodings); + foreach (QByteArray encoding, encodingList) { + temp << encoding << ", "; + } + qCritical("required string-encoding not supported. Available codecs: %s", + encodings.toUtf8().constData()); + + throw std::runtime_error(QObject::tr("encoding error, please report this as a bug and include the file " + "mo_interface.log!").toUtf8().constData()); + } + + file.resize(0); + + file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); + + bool invalidFileNames = false; + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + int priority = m_ESPsByPriority[i]; + if ((m_ESPs[priority].m_Enabled || writeUnchecked) && !m_ESPs[priority].m_Removed) { + //file.write(m_ESPs[priority].m_Name.toUtf8()); + if (!textCodec->canEncode(m_ESPs[priority].m_Name)) { + 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("\r\n"); + } + } + 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.")); + } + + 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)); + } + + 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.close(); +} + + +void PluginList::saveTo(const QString &pluginFileName, const QString &loadOrderFileName, const QString &lockedOrderFileName, const QString& deleterFileName, bool hideUnchecked) const +{ + if (!m_Modified) { + return; + } + + writePlugins(pluginFileName, false); + writePlugins(loadOrderFileName, true); + writeLockedOrder(lockedOrderFileName); + + if (hideUnchecked) { + QFile deleterFile(deleterFileName); + deleterFile.open(QIODevice::WriteOnly); + deleterFile.resize(0); + deleterFile.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + int priority = m_ESPsByPriority[i]; + if (m_ESPs[priority].m_Removed) { + deleterFile.write(m_ESPs[priority].m_Name.toUtf8()); + deleterFile.write("\r\n"); + } + } + deleterFile.close(); + qDebug("%s saved", QDir::toNativeSeparators(deleterFileName).toUtf8().constData()); + } + + m_Modified = false; +} + + +bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) +{ + if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) { + // nothing to do + return true; + } + + for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + std::wstring espName = ToWString(iter->m_Name); + const FileEntry *fileEntry = directoryStructure.findFile(espName); + if (fileEntry != NULL) { + QString fileName; + bool archive = false; + int originid = fileEntry->getOrigin(archive); + fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name); + + HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + if (::GetLastError() == ERROR_SHARING_VIOLATION) { + // file is locked, probably the game is running + return false; + } else { + throw windows_error(QObject::tr("failed to access %1").arg(fileName).toUtf8().constData()); + } + } + + ULONGLONG temp = 0; + temp = (145731ULL + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL; + + FILETIME newWriteTime; + + newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); + newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); + iter->m_Time = newWriteTime; + fileEntry->setFileTime(newWriteTime); + if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) { + throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); + } + + CloseHandle(file); + } + } + return true; +} + +bool PluginList::isESPLocked(int index) const +{ + return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); +} + + +void PluginList::lockESPIndex(int index, bool lock) +{ + if (lock) { + m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; + } else { + auto iter = m_LockedOrder.find(getName(index).toLower()); + if (iter != m_LockedOrder.end()) { + m_LockedOrder.erase(iter); + } + } + m_Modified = true; + emit esplist_changed(); +} + + +void PluginList::syncLoadOrder() +{ + int loadOrder = 0; + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + int index = m_ESPsByPriority[i]; + + if (m_ESPs[index].m_Enabled) { + m_ESPs[index].m_LoadOrder = loadOrder++; + } else { + m_ESPs[index].m_LoadOrder = -1; + } + } +} + +void PluginList::refreshLoadOrder() +{ + syncLoadOrder(); + + // set priorities according to locked load order + std::map<int, QString> lockedLoadOrder; + std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), + [&lockedLoadOrder] (const std::pair<QString, int> &ele) { lockedLoadOrder[ele.second] = ele.first; }); + + int targetPrio = 0; + // this is guaranteed to iterate from lowest key (load order) to highest + for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { + auto nameIter = m_ESPsByName.find(iter->second); + if (nameIter != m_ESPsByName.end()) { + // locked esp exists + + // find the location to insert at + while ((targetPrio < static_cast<int>(m_ESPs.size())) && + (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) { + if (QString::compare(m_ESPs[m_ESPsByPriority[targetPrio]].m_Name, iter->second) != 0) { + ++targetPrio; + } + } + int temp = targetPrio; + if (m_ESPs[nameIter->second].m_Priority != temp) { + setPluginPriority(nameIter->second, temp); + m_ESPs[nameIter->second].m_LoadOrder = iter->first; + syncLoadOrder(); + m_Modified = true; + } + } + } +} + + +void PluginList::updateIndices() +{ + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + m_ESPsByPriority.resize(m_ESPs.size()); + + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; + m_ESPsByPriority[m_ESPs[i].m_Priority] = i; + } +} + + +int PluginList::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return m_ESPs.size(); + } else { + return 0; + } +} + +int PluginList::columnCount(const QModelIndex &) const +{ + return 3; +} + + +QVariant PluginList::data(const QModelIndex &modelIndex, int role) const +{ + int index = modelIndex.row(); + + if (role == Qt::DisplayRole) { + switch (modelIndex.column()) { + case COL_NAME: { + return m_ESPs[index].m_Name; + } break; + case COL_PRIORITY: { + if (m_ESPs[index].m_Priority == 0) { + return tr("min"); + } else if (m_ESPs[index].m_Priority == m_ESPs.size() - 1) { + return tr("max"); + } else { + return QString::number(m_ESPs[index].m_Priority); + } + } break; + case COL_MODINDEX: { + if (m_ESPs[index].m_LoadOrder == -1) { + return QString(); + } else { + return QString("%1").arg(m_ESPs[index].m_LoadOrder, 2, 16, QChar('0')).toUpper(); + } + } break; + default: { + return QVariant(); + } break; + } + } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) { + if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { + return QIcon(":/MO/gui/locked"); + } 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::TextAlignmentRole) { + if (modelIndex.column() == 0) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } + } else if (role == Qt::ToolTipRole) { + if (m_ESPs[index].m_ForceEnabled) { + return tr("This plugin can't be disabled (enforced by the game)"); + } else { + return tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + } + } else { + return QVariant(); + } +} + + +bool PluginList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == Qt::CheckStateRole) { + m_ESPs[index.row()].m_Enabled = value.toInt() == Qt::Checked; + + emit layoutAboutToBeChanged(); + + refreshLoadOrder(); + m_Modified = true; + emit esplist_changed(); + + emit layoutChanged(); + return true; + } else { + return false; + } +} + + +QVariant PluginList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + return getColumnName(section); + } else if (role == Qt::ToolTipRole) { + return getColumnToolTip(section); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 25; + temp.rheight() += 12; + return temp; + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + + +Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + Qt::ItemFlags result = QAbstractTableModel::flags(modelIndex); + + if (modelIndex.isValid()) { + if ((m_ESPs[index].m_ForceEnabled)) { + result &= ~Qt::ItemIsEnabled; + } + result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; + } else { + result |= Qt::ItemIsDropEnabled; + } + + return result; +} + + +void PluginList::setPluginPriority(int row, int &newPriority) +{ + int newPriorityTemp = newPriority; + + QString sourceExtension = m_ESPs[row].m_Name.right(3).toLower(); + if (sourceExtension == "esp") { + // don't allow esps to be moved above esms + while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) && + (m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_Name.right(3).toLower() == "esm")) { + ++newPriorityTemp; + } + } else { + // don't allow esms to be moved below esps + while ((newPriorityTemp > 0) && + (m_ESPs[m_ESPsByPriority[newPriorityTemp - 1]].m_Name.right(3).toLower() == "esp")) { + --newPriorityTemp; + } + // also don't allow "regular" esms to be moved above primary plugins + while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) && + (m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_ForceEnabled)) { + ++newPriorityTemp; + } + } + + int oldPriority = m_ESPs[row].m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ESPs[m_ESPsByPriority[i]].m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ESPs[m_ESPsByPriority[i]].m_Priority; + } + ++newPriority; + } + + m_ESPs[row].m_Priority = newPriorityTemp; + updateIndices(); +} + + +void PluginList::changePluginPriority(std::vector<int> rows, int newPriority) +{ + emit layoutAboutToBeChanged(); + // sort rows to insert by their old priority (ascending) and insert them move them in that order + const std::vector<ESPInfo> &esp = m_ESPs; + std::sort(rows.begin(), rows.end(), + [&esp](const int &LHS, const int &RHS) { + return esp[LHS].m_Priority < esp[RHS].m_Priority; + }); + + // odd stuff: if any of the dragged sources has priority lower than the destination then the + // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? + for (std::vector<int>::const_iterator iter = rows.begin(); + iter != rows.end(); ++iter) { + if (m_ESPs[*iter].m_Priority < newPriority) { + --newPriority; + break; + } + } + + for (std::vector<int>::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { + setPluginPriority(*iter, newPriority); + } + refreshLoadOrder(); + + emit esplist_changed(); + + m_Modified = true; + + emit layoutChanged(); +} + + +bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + std::vector<int> sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap<int, QVariant> roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { // only add each row once + sourceRows.push_back(sourceRow); + } + } + + if (row == -1) { + row = parent.row(); + } + + int newPriority = 0; + + if ((row < 0) || + (row >= static_cast<int>(m_ESPs.size()))) { + newPriority = m_ESPs.size(); + } else { + newPriority = m_ESPs[row].m_Priority; + } + changePluginPriority(sourceRows, newPriority); + + + return false; +} + + +bool PluginList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj); + QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); + if ((itemView != NULL) && + (keyEvent->modifiers() == Qt::ControlModifier) && + ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model()); + int diff = -1; + if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { + diff = 1; + } + QModelIndexList rows = selectionModel->selectedRows(); + if (keyEvent->key() == Qt::Key_Down) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + foreach (QModelIndex idx, rows) { + if (proxyModel != NULL) { + idx = proxyModel->mapToSource(idx); + } + int newPriority = m_ESPs[idx.row()].m_Priority + diff; + if ((newPriority >= 0) && (newPriority < rowCount())) { + setPluginPriority(idx.row(), newPriority); + emit dataChanged(this->index(idx.row(), 0), this->index(idx.row(), this->columnCount() - 1)); + } + } + refreshLoadOrder(); + return true; + } + } + return QObject::eventFilter(obj, event); +} diff --git a/src/pluginlist.h b/src/pluginlist.h index aa6d1426..a46fd4d5 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -59,7 +59,7 @@ public: * @param baseDirectory the root directory structure representing the virtual data directory * @todo the profile is not used? If it was, we should pass the Profile-object instead **/ - void refresh(const QString &profileName, const DirectoryEntry &baseDirectory, const QString &pluginsFile, const QString &loadOrderFile, const QString &lockedOrderFile); + void refresh(const QString &profileName, const MOShared::DirectoryEntry &baseDirectory, const QString &pluginsFile, const QString &loadOrderFile, const QString &lockedOrderFile); /** * @brief enable a plugin based on its name @@ -117,7 +117,7 @@ public: * in different mods can also have different load orders which makes this very intransparent * @note also stores to disk the list of locked esps **/ - bool saveLoadOrder(DirectoryEntry &directoryStructure); + bool saveLoadOrder(MOShared::DirectoryEntry &directoryStructure); QString getName(int index) const { return m_ESPs.at(index).m_Name; } int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } diff --git a/src/profile.cpp b/src/profile.cpp index 21cd1216..4892fd78 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -17,677 +17,679 @@ 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 "profile.h"
-#include "report.h"
-#include "gameinfo.h"
-#include "windows_error.h"
-#include "dummybsa.h"
-#include "modinfo.h"
-#include <utility.h>
-#include <util.h>
-#include <appconfig.h>
-#include <QMessageBox>
-#include <QApplication>
-#include <QSettings>
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <shlobj.h>
-#include <stdexcept>
-
-
-Profile::Profile()
-{
- initTimer();
-}
-
-Profile::Profile(const QString &name, bool useDefaultSettings)
-{
- QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()));
- QDir profileBase(profilesDir);
-
- if (!profileBase.exists() || !profileBase.mkdir(name)) {
- throw std::runtime_error(QObject::tr("failed to create %1").arg(name).toUtf8().constData());
- }
- QString fullPath = profilesDir + "/" + name;
- m_Directory = QDir(fullPath);
- QFile modList(m_Directory.filePath("modlist.txt"));
- if (!modList.open(QIODevice::ReadWrite)) {
- profileBase.rmdir(name);
- throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData());
- }
- modList.close();
-
- try {
- GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings);
- } catch (...) {
- // clean up in case of an error
- removeDir(profileBase.absoluteFilePath(name));
- throw;
- }
- initTimer();
- refreshModStatus();
-}
-
-
-Profile::Profile(const QDir& directory)
- : m_Directory(directory)
-{
- if (!QFile::exists(m_Directory.filePath("modlist.txt"))) {
- throw std::runtime_error(QObject::tr("modlist.txt missing").toUtf8().constData());
- }
-
- GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath()));
-
- if (!QFile::exists(getIniFileName())) {
- reportError(QObject::tr("\"%1\" is missing").arg(getIniFileName()));
- }
- initTimer();
- refreshModStatus();
-}
-
-
-Profile::Profile(const Profile& reference)
- : m_Directory(reference.m_Directory)
-{
- initTimer();
- refreshModStatus();
-}
-
-
-Profile::~Profile()
-{
- writeModlistNow();
-}
-
-
-void Profile::initTimer()
-{
- m_SaveTimer = new QTimer(this);
- m_SaveTimer->setSingleShot(true);
- connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow()));
-}
-
-
-bool Profile::exists() const
-{
- return m_Directory.exists();
-}
-
-
-void Profile::writeModlist() const
-{
- if (!m_SaveTimer->isActive()) {
- m_SaveTimer->start(2000);
- }
-}
-
-
-void Profile::cancelWriteModlist() const
-{
- m_SaveTimer->stop();
-}
-
-
-void Profile::writeModlistNow() const
-{
- m_SaveTimer->stop();
-#pragma message("right now, we're doing unnecessary saves. We need a flag that says that mod priority, enabled-state or name of a mod has changed")
-
- QString fileName = getModlistFileName();
- QFile file(fileName);
- file.open(QIODevice::WriteOnly);
- file.resize(0);
- file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8());
- if (m_ModStatus.empty()) {
- return;
- }
-
- for (int i = m_ModStatus.size() - 1; i >= 0; --i) {
- // the priority order was inverted on load so it has to be inverted again
- unsigned int index = m_ModIndexByPriority[i];
- if (index != UINT_MAX) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
- if (modInfo->getFixedPriority() == INT_MIN) {
- if (m_ModStatus[index].m_Enabled) {
- file.write("+");
- } else {
- file.write("-");
- }
- file.write(modInfo->name().toUtf8());
- file.write("\r\n");
- }
- }
- }
-
- file.close();
- qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData());
-}
-
-
-void Profile::createTweakedIniFile()
-{
- QFileInfo iniInfo(getIniFileName());
-
- QString tweakedIni = iniInfo.absolutePath() + "/initweaks.ini";
-// QFile iniFile(tweakedIni);
-
- // workaround: the fallout nv launcher seems to mark the file read-only. crazy...
-/* ::SetFileAttributesW(ToWString(tweakedIni).c_str(), FILE_ATTRIBUTE_NORMAL);
- QFile(tweakedIni).remove();
- if (!iniFile.copy(tweakedIni)) {
- reportError(tr("failed to apply ini tweaks").append(": ").append(iniFile.errorString()));
- return;
- }*/
-
- QFile::remove(tweakedIni); // remove the old ini tweaks
-
- for (unsigned int i = 0; i < m_ModStatus.size(); ++i) {
- if (m_ModStatus[i].m_Enabled) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
- mergeTweaks(modInfo, tweakedIni);
- }
- }
-}
-
-
-void Profile::refreshModStatus()
-{
- // don't lose changes!
- if (m_SaveTimer->isActive()) {
- writeModlistNow();
- }
-
- QFile file(getModlistFileName());
- if (!file.exists()) {
- throw MyException(QObject::tr("failed to find \"%1\"").arg(getModlistFileName()));
- }
-
- bool modStatusModified = false;
- m_ModStatus.clear();
- m_ModStatus.resize(ModInfo::getNumMods());
-
- std::set<QString> namesRead;
-
- // load mods from file and update enabled state and priority for them
- file.open(QIODevice::ReadOnly);
- int index = 0;
- while (!file.atEnd()) {
- QByteArray line = file.readLine();
- bool enabled = true;
- QString modName;
- if (line.length() == 0) {
- // empty line
- continue;
- } else if (line.at(0) == '#') {
- // comment line
- continue;
- } else if (line.at(0) == '-') {
- enabled = false;
- modName = QString::fromUtf8(line.mid(1).trimmed().constData());
- } else if (line.at(0) == '+') {
- modName = QString::fromUtf8(line.mid(1).trimmed().constData());
- } else {
- modName = QString::fromUtf8(line.trimmed().constData());
- }
- if (modName.size() > 0) {
- if (namesRead.find(modName) != namesRead.end()) {
- continue;
- } else {
- namesRead.insert(modName);
- }
- unsigned int modindex = ModInfo::getIndex(modName);
- if (modindex != UINT_MAX) {
- ModInfo::Ptr info = ModInfo::getByIndex(modindex);
- if ((modindex < m_ModStatus.size()) && (info->getFixedPriority() == INT_MIN)) {
- m_ModStatus[modindex].m_Enabled = enabled;
- if (m_ModStatus[modindex].m_Priority == -1) {
- if (static_cast<size_t>(index) >= m_ModStatus.size()) {
- throw MyException(tr("invalid index %1").arg(index));
- }
- m_ModStatus[modindex].m_Priority = index++;
- }
- } else {
- qDebug("mod \"%s\" (profile \"%s\") not found",
- modName.toUtf8().constData(), m_Directory.path().toUtf8().constData());
- // need to rewrite the modlist to fix this
- modStatusModified = true;
- }
- }
- } else {
- // line was empty after trimming
- }
- }
-
- int numKnownMods = index;
-
- // invert priority order to match that of the pluginlist. Also
- // give priorities to mods not referenced in the profile
- for (size_t i = 0; i < m_ModStatus.size(); ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
- if (!modInfo->canBeEnabled()) {
- continue;
- }
- if (m_ModStatus[i].m_Priority != -1) {
- m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1;
- } else {
- if (static_cast<size_t>(index) >= m_ModStatus.size()) {
- throw MyException(tr("invalid index %1").arg(index));
- }
- m_ModStatus[i].m_Priority = index++;
- // also, mark the mod-list as changed
- modStatusModified = true;
- }
- }
-
- file.close();
- updateIndices();
- if (modStatusModified) {
- writeModlist();
- }
-}
-
-
-void Profile::dumpModStatus() const
-{
- for (unsigned int i = 0; i < m_ModStatus.size(); ++i) {
- ModInfo::Ptr info = ModInfo::getByIndex(i);
- qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority,
- m_ModStatus[i].m_Enabled ? "enabled" : "disabled");
- }
-}
-
-
-void Profile::updateIndices()
-{
- m_NumRegularMods = 0;
- m_ModIndexByPriority.clear();
- m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX);
- for (unsigned int i = 0; i < m_ModStatus.size(); ++i) {
- int priority = m_ModStatus[i].m_Priority;
- if (priority < 0) {
- continue;
- } else if (priority >= static_cast<int>(m_ModIndexByPriority.size())) {
- qCritical("invalid priority %d for mod", priority);
- continue;
- } else {
- ++m_NumRegularMods;
- m_ModIndexByPriority.at(priority) = i;
- }
- }
-}
-
-
-std::vector<std::tuple<QString, QString, int> > Profile::getActiveMods()
-{
- std::vector<std::tuple<QString, QString, int> > result;
- for (std::vector<unsigned int>::const_iterator iter = m_ModIndexByPriority.begin();
- iter != m_ModIndexByPriority.end(); ++iter) {
- if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter);
- result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority));
- }
- }
-
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
-
- if (overwriteIndex != UINT_MAX) {
- ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex);
- result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX));
- } else {
- reportError(tr("Overwrite directory couldn't be parsed"));
- }
- return result;
-}
-
-
-unsigned int Profile::modIndexByPriority(unsigned int priority) const
-{
- if (priority >= m_ModStatus.size()) {
- throw MyException(tr("invalid priority %1").arg(priority));
- }
-
- return m_ModIndexByPriority[priority];
-}
-
-
-void Profile::setModEnabled(unsigned int index, bool enabled)
-{
- if (index >= m_ModStatus.size()) {
- throw MyException(tr("invalid index %1").arg(index));
- }
-
- if (m_ModStatus[index].m_Overwrite) {
- // overwrite is always enabled
- return;
- }
-
- if (enabled != m_ModStatus[index].m_Enabled) {
- m_ModStatus[index].m_Enabled = enabled;
- emit modStatusChanged(index);
- }
-}
-
-
-bool Profile::modEnabled(unsigned int index) const
-{
- if (index >= m_ModStatus.size()) {
- throw MyException(tr("invalid index %1").arg(index));
- }
-
- return m_ModStatus[index].m_Enabled;
-}
-
-
-int Profile::getModPriority(unsigned int index) const
-{
- if (index >= m_ModStatus.size()) {
- throw MyException(tr("invalid index %1").arg(index));
- }
-
- return m_ModStatus[index].m_Priority;
-}
-
-
-void Profile::setModPriority(unsigned int index, int &newPriority)
-{
- int newPriorityTemp = (std::max)(0, (std::min<int>)(m_ModStatus.size() - 1, newPriority));
- if (m_ModStatus[index].m_Overwrite) {
- // can't change priority of the overwrite
- return;
- }
- int oldPriority = m_ModStatus[index].m_Priority;
- if (newPriorityTemp > oldPriority) {
- // priority is higher than the old, so the gap we left is in lower priorities
- for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) {
- --m_ModStatus[m_ModIndexByPriority[i]].m_Priority;
- }
- } else {
- for (int i = newPriorityTemp; i < oldPriority; ++i) {
- ++m_ModStatus[m_ModIndexByPriority[i]].m_Priority;
- }
- ++newPriority;
- }
-
- m_ModStatus[index].m_Priority = newPriorityTemp;
-
- updateIndices();
- writeModlist();
-}
-
-
-Profile Profile::createFrom(const QString &name, const Profile &reference)
-{
- QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name);
- reference.copyFilesTo(profileDirectory);
- return Profile(QDir(profileDirectory));
-}
-
-
-void Profile::copyFilesTo(QString &target) const
-{
- copyDir(m_Directory.absolutePath(), target, false);
-}
-
-
-std::vector<std::wstring> Profile::splitDZString(const wchar_t *buffer) const
-{
- std::vector<std::wstring> result;
- const wchar_t *pos = buffer;
- size_t length = wcslen(pos);
- while (length != 0U) {
- result.push_back(pos);
- pos += length + 1;
- length = wcslen(pos);
- }
- return result;
-}
-
-
-void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const
-{
- static const int bufferSize = 32768;
-
- std::wstring tweakNameW = ToWString(tweakName);
- std::wstring tweakedIniW = ToWString(tweakedIni);
- QScopedArrayPointer<wchar_t> buffer(new wchar_t[bufferSize]);
-
- // retrieve a list of sections
- DWORD size = ::GetPrivateProfileSectionNamesW(
- buffer.data(), bufferSize, tweakNameW.c_str());
-
- if (size == bufferSize - 2) {
- // unfortunately there is no good way to find the required size
- // of the buffer
- throw MyException(QString("Buffer too small. Please report this as a bug. "
- "For now you might want to split up %1").arg(tweakName));
- }
-
- std::vector<std::wstring> sections = splitDZString(buffer.data());
-
- // now iterate over all sections and retrieve a list of keys in each
- for (std::vector<std::wstring>::iterator iter = sections.begin();
- iter != sections.end(); ++iter) {
- // retrieve the names of all keys
- size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(),
- bufferSize, tweakNameW.c_str());
- if (size == bufferSize - 2) {
- throw MyException(QString("Buffer too small. Please report this as a bug. "
- "For now you might want to split up %1").arg(tweakName));
- }
-
- std::vector<std::wstring> keys = splitDZString(buffer.data());
-
- for (std::vector<std::wstring>::iterator keyIter = keys.begin();
- keyIter != keys.end(); ++keyIter) {
- //TODO this treats everything as strings but how could I differentiate the type?
- ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(),
- NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str());
- ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(),
- buffer.data(), tweakedIniW.c_str());
- }
- }
-}
-
-void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const
-{
- std::vector<QString> iniTweaks = modInfo->getIniTweaks();
- for (std::vector<QString>::iterator iter = iniTweaks.begin();
- iter != iniTweaks.end(); ++iter) {
- mergeTweak(*iter, tweakedIni);
- }
-
- ::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str());
-
- if (localSavesEnabled()) {
- ::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str());
-
- ::WritePrivateProfileStringW(L"General", L"SLocalSavePath",
- AppConfig::localSavePlaceholder(),
- ToWString(tweakedIni).c_str());
- }
-}
-
-
-bool Profile::invalidationActive(bool *supported) const
-{
- if (GameInfo::instance().requiresBSAInvalidation()) {
- *supported = true;
- wchar_t buffer[1024];
- std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
- if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
- L"", buffer, 1024, iniFileName.c_str()) == 0) {
- if (errno != 0x02) {
- if (supported != NULL) {
- *supported = false;
- }
- return false;
- } else {
- qCritical("failed to parse \"%ls\"", iniFileName.c_str());
- QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError());
- throw windows_error(errorMessage.toUtf8().constData());
- }
- }
- QStringList archives = ToQString(buffer).split(',');
-
- for (int i = 0; i < archives.count(); ++i) {
- QString bsaName = archives.at(i).trimmed();
- if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) {
- return true;
- }
- }
- } else {
- *supported = false;
- }
- return false;
-}
-
-
-void Profile::deactivateInvalidation() const
-{
- if (GameInfo::instance().requiresBSAInvalidation()) {
- wchar_t buffer[1024];
- std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
- if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
- L"", buffer, 1024, iniFileName.c_str()) == 0) {
- if (errno == 0x02) {
- QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError());
- throw windows_error(errorMessage.toUtf8().constData());
- } else {
- return;
- }
- }
- QStringList archives = ToQString(buffer).split(", ");
-
- for (int i = 0; i < archives.count();) {
- QString bsaName = archives.at(i).trimmed();
- if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) {
- archives.removeAt(i);
- } else {
- ++i;
- }
- }
-
- if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
- ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) ||
- !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) ||
- !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) {
- throw windows_error("failed to modify ini file");
- }
- }
-}
-
-
-void Profile::activateInvalidation(const QString& dataDirectory) const
-{
- if (GameInfo::instance().requiresBSAInvalidation()) {
- wchar_t buffer[1024];
- std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
- if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
- L"", buffer, 1024, iniFileName.c_str()) == 0) {
- throw windows_error("failed to parse ini file");
- }
- QStringList archives = ToQString(buffer).split(", ");
-
- QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA());
-
- if (!archives.contains(invalidationBSA)) {
- archives.insert(0, invalidationBSA);
- }
-
- if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
- ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) ||
- !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) ||
- !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) {
- throw windows_error("failed to modify ini file");
- }
-
- QString bsaFile = dataDirectory + "/" + invalidationBSA;
- if (!QFile::exists(bsaFile)) {
- DummyBSA bsa;
- bsa.write(bsaFile);
- }
- }
-}
-
-
-bool Profile::localSavesEnabled() const
-{
- return m_Directory.exists("saves");
-}
-
-
-bool Profile::enableLocalSaves(bool enable)
-{
- if (enable) {
- if (m_Directory.exists("_saves")) {
- m_Directory.rename("_saves", "saves");
- } else {
- m_Directory.mkdir("saves");
- }
- } else {
-
- QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"),
- tr("Do you want to delete local savegames? (If you select \"No\", the save games "
- "will show up again if you re-enable local savegames)"),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
- QMessageBox::Cancel);
- if (res == QMessageBox::Yes) {
- removeDir(m_Directory.absoluteFilePath("_saves"));
- } else if (res == QMessageBox::No) {
- m_Directory.rename("saves", "_saves");
- } else {
- return false;
- }
- }
-
- // default: assume success
- return true;
-}
-
-
-QString Profile::getModlistFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt"));
-}
-
-QString Profile::getPluginsFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt"));
-}
-
-
-QString Profile::getLoadOrderFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt"));
-}
-
-
-QString Profile::getLockedOrderFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt"));
-}
-
-
-QString Profile::getArchivesFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt"));
-}
-
-QString Profile::getDeleterFileName() const
-{
- return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt"));
-}
-
-
-QString Profile::getIniFileName() const
-{
- std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin());
- return m_Directory.absoluteFilePath(ToQString(primaryIniFile));
-}
-
-QString Profile::getPath() const
-{
- return QDir::cleanPath(m_Directory.absolutePath());
-}
+#include "profile.h" +#include "report.h" +#include "gameinfo.h" +#include "windows_error.h" +#include "dummybsa.h" +#include "modinfo.h" +#include <utility.h> +#include <util.h> +#include <appconfig.h> +#include <QMessageBox> +#include <QApplication> +#include <QSettings> +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <shlobj.h> +#include <stdexcept> + +using namespace MOBase; +using namespace MOShared; + +Profile::Profile() +{ + initTimer(); +} + +Profile::Profile(const QString &name, bool useDefaultSettings) +{ + QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); + QDir profileBase(profilesDir); + + if (!profileBase.exists() || !profileBase.mkdir(name)) { + throw std::runtime_error(QObject::tr("failed to create %1").arg(name).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + name; + m_Directory = QDir(fullPath); + QFile modList(m_Directory.filePath("modlist.txt")); + if (!modList.open(QIODevice::ReadWrite)) { + profileBase.rmdir(name); + throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); + } + modList.close(); + + try { + GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); + } catch (...) { + // clean up in case of an error + removeDir(profileBase.absoluteFilePath(name)); + throw; + } + initTimer(); + refreshModStatus(); +} + + +Profile::Profile(const QDir& directory) + : m_Directory(directory) +{ + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { + throw std::runtime_error(QObject::tr("modlist.txt missing").toUtf8().constData()); + } + + GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); + + if (!QFile::exists(getIniFileName())) { + reportError(QObject::tr("\"%1\" is missing").arg(getIniFileName())); + } + initTimer(); + refreshModStatus(); +} + + +Profile::Profile(const Profile& reference) + : m_Directory(reference.m_Directory) +{ + initTimer(); + refreshModStatus(); +} + + +Profile::~Profile() +{ + writeModlistNow(); +} + + +void Profile::initTimer() +{ + m_SaveTimer = new QTimer(this); + m_SaveTimer->setSingleShot(true); + connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); +} + + +bool Profile::exists() const +{ + return m_Directory.exists(); +} + + +void Profile::writeModlist() const +{ + if (!m_SaveTimer->isActive()) { + m_SaveTimer->start(2000); + } +} + + +void Profile::cancelWriteModlist() const +{ + m_SaveTimer->stop(); +} + + +void Profile::writeModlistNow() const +{ + m_SaveTimer->stop(); +#pragma message("right now, we're doing unnecessary saves. We need a flag that says that mod priority, enabled-state or name of a mod has changed") + + QString fileName = getModlistFileName(); + QFile file(fileName); + file.open(QIODevice::WriteOnly); + file.resize(0); + file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + if (m_ModStatus.empty()) { + return; + } + + for (int i = m_ModStatus.size() - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + if (index != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->getFixedPriority() == INT_MIN) { + if (m_ModStatus[index].m_Enabled) { + file.write("+"); + } else { + file.write("-"); + } + file.write(modInfo->name().toUtf8()); + file.write("\r\n"); + } + } + } + + file.close(); + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); +} + + +void Profile::createTweakedIniFile() +{ + QFileInfo iniInfo(getIniFileName()); + + QString tweakedIni = iniInfo.absolutePath() + "/initweaks.ini"; +// QFile iniFile(tweakedIni); + + // workaround: the fallout nv launcher seems to mark the file read-only. crazy... +/* ::SetFileAttributesW(ToWString(tweakedIni).c_str(), FILE_ATTRIBUTE_NORMAL); + QFile(tweakedIni).remove(); + if (!iniFile.copy(tweakedIni)) { + reportError(tr("failed to apply ini tweaks").append(": ").append(iniFile.errorString())); + return; + }*/ + + QFile::remove(tweakedIni); // remove the old ini tweaks + + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + if (m_ModStatus[i].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + mergeTweaks(modInfo, tweakedIni); + } + } +} + + +void Profile::refreshModStatus() +{ + // don't lose changes! + if (m_SaveTimer->isActive()) { + writeModlistNow(); + } + + QFile file(getModlistFileName()); + if (!file.exists()) { + throw MyException(QObject::tr("failed to find \"%1\"").arg(getModlistFileName())); + } + + bool modStatusModified = false; + m_ModStatus.clear(); + m_ModStatus.resize(ModInfo::getNumMods()); + + std::set<QString> namesRead; + + // load mods from file and update enabled state and priority for them + file.open(QIODevice::ReadOnly); + int index = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine(); + bool enabled = true; + QString modName; + if (line.length() == 0) { + // empty line + continue; + } else if (line.at(0) == '#') { + // comment line + continue; + } else if (line.at(0) == '-') { + enabled = false; + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else if (line.at(0) == '+') { + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else { + modName = QString::fromUtf8(line.trimmed().constData()); + } + if (modName.size() > 0) { + if (namesRead.find(modName) != namesRead.end()) { + continue; + } else { + namesRead.insert(modName); + } + unsigned int modindex = ModInfo::getIndex(modName); + if (modindex != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(modindex); + if ((modindex < m_ModStatus.size()) && (info->getFixedPriority() == INT_MIN)) { + m_ModStatus[modindex].m_Enabled = enabled; + if (m_ModStatus[modindex].m_Priority == -1) { + if (static_cast<size_t>(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + m_ModStatus[modindex].m_Priority = index++; + } + } else { + qDebug("mod \"%s\" (profile \"%s\") not found", + modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; + } + } + } else { + // line was empty after trimming + } + } + + int numKnownMods = index; + + // invert priority order to match that of the pluginlist. Also + // give priorities to mods not referenced in the profile + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + if (!modInfo->canBeEnabled()) { + continue; + } + if (m_ModStatus[i].m_Priority != -1) { + m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; + } else { + if (static_cast<size_t>(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + m_ModStatus[i].m_Priority = index++; + // also, mark the mod-list as changed + modStatusModified = true; + } + } + + file.close(); + updateIndices(); + if (modStatusModified) { + writeModlist(); + } +} + + +void Profile::dumpModStatus() const +{ + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + } +} + + +void Profile::updateIndices() +{ + m_NumRegularMods = 0; + m_ModIndexByPriority.clear(); + m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + int priority = m_ModStatus[i].m_Priority; + if (priority < 0) { + continue; + } else if (priority >= static_cast<int>(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; + } + } +} + + +std::vector<std::tuple<QString, QString, int> > Profile::getActiveMods() +{ + std::vector<std::tuple<QString, QString, int> > result; + for (std::vector<unsigned int>::const_iterator iter = m_ModIndexByPriority.begin(); + iter != m_ModIndexByPriority.end(); ++iter) { + if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); + result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); + } + } + + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector<ModInfo::EFlag> flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + if (overwriteIndex != UINT_MAX) { + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); + } else { + reportError(tr("Overwrite directory couldn't be parsed")); + } + return result; +} + + +unsigned int Profile::modIndexByPriority(unsigned int priority) const +{ + if (priority >= m_ModStatus.size()) { + throw MyException(tr("invalid priority %1").arg(priority)); + } + + return m_ModIndexByPriority[priority]; +} + + +void Profile::setModEnabled(unsigned int index, bool enabled) +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + if (m_ModStatus[index].m_Overwrite) { + // overwrite is always enabled + return; + } + + if (enabled != m_ModStatus[index].m_Enabled) { + m_ModStatus[index].m_Enabled = enabled; + emit modStatusChanged(index); + } +} + + +bool Profile::modEnabled(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Enabled; +} + + +int Profile::getModPriority(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Priority; +} + + +void Profile::setModPriority(unsigned int index, int &newPriority) +{ + int newPriorityTemp = (std::max)(0, (std::min<int>)(m_ModStatus.size() - 1, newPriority)); + if (m_ModStatus[index].m_Overwrite) { + // can't change priority of the overwrite + return; + } + int oldPriority = m_ModStatus[index].m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ModStatus[m_ModIndexByPriority[i]].m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ModStatus[m_ModIndexByPriority[i]].m_Priority; + } + ++newPriority; + } + + m_ModStatus[index].m_Priority = newPriorityTemp; + + updateIndices(); + writeModlist(); +} + + +Profile Profile::createFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return Profile(QDir(profileDirectory)); +} + + +void Profile::copyFilesTo(QString &target) const +{ + copyDir(m_Directory.absolutePath(), target, false); +} + + +std::vector<std::wstring> Profile::splitDZString(const wchar_t *buffer) const +{ + std::vector<std::wstring> result; + const wchar_t *pos = buffer; + size_t length = wcslen(pos); + while (length != 0U) { + result.push_back(pos); + pos += length + 1; + length = wcslen(pos); + } + return result; +} + + +void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const +{ + static const int bufferSize = 32768; + + std::wstring tweakNameW = ToWString(tweakName); + std::wstring tweakedIniW = ToWString(tweakedIni); + QScopedArrayPointer<wchar_t> buffer(new wchar_t[bufferSize]); + + // retrieve a list of sections + DWORD size = ::GetPrivateProfileSectionNamesW( + buffer.data(), bufferSize, tweakNameW.c_str()); + + if (size == bufferSize - 2) { + // unfortunately there is no good way to find the required size + // of the buffer + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector<std::wstring> sections = splitDZString(buffer.data()); + + // now iterate over all sections and retrieve a list of keys in each + for (std::vector<std::wstring>::iterator iter = sections.begin(); + iter != sections.end(); ++iter) { + // retrieve the names of all keys + size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(), + bufferSize, tweakNameW.c_str()); + if (size == bufferSize - 2) { + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector<std::wstring> keys = splitDZString(buffer.data()); + + for (std::vector<std::wstring>::iterator keyIter = keys.begin(); + keyIter != keys.end(); ++keyIter) { + //TODO this treats everything as strings but how could I differentiate the type? + ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), + NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); + ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), + buffer.data(), tweakedIniW.c_str()); + } + } +} + +void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const +{ + std::vector<QString> iniTweaks = modInfo->getIniTweaks(); + for (std::vector<QString>::iterator iter = iniTweaks.begin(); + iter != iniTweaks.end(); ++iter) { + mergeTweak(*iter, tweakedIni); + } + + ::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str()); + + if (localSavesEnabled()) { + ::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str()); + + ::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str()); + } +} + + +bool Profile::invalidationActive(bool *supported) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + *supported = true; + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno != 0x02) { + if (supported != NULL) { + *supported = false; + } + return false; + } else { + qCritical("failed to parse \"%ls\"", iniFileName.c_str()); + QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); + throw windows_error(errorMessage.toUtf8().constData()); + } + } + QStringList archives = ToQString(buffer).split(','); + + for (int i = 0; i < archives.count(); ++i) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + return true; + } + } + } else { + *supported = false; + } + return false; +} + + +void Profile::deactivateInvalidation() const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); + throw windows_error(errorMessage.toUtf8().constData()); + } else { + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + for (int i = 0; i < archives.count();) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + archives.removeAt(i); + } else { + ++i; + } + } + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { + throw windows_error("failed to modify ini file"); + } + } +} + + +void Profile::activateInvalidation(const QString& dataDirectory) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + throw windows_error("failed to parse ini file"); + } + QStringList archives = ToQString(buffer).split(", "); + + QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); + + if (!archives.contains(invalidationBSA)) { + archives.insert(0, invalidationBSA); + } + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { + throw windows_error("failed to modify ini file"); + } + + QString bsaFile = dataDirectory + "/" + invalidationBSA; + if (!QFile::exists(bsaFile)) { + DummyBSA bsa; + bsa.write(bsaFile); + } + } +} + + +bool Profile::localSavesEnabled() const +{ + return m_Directory.exists("saves"); +} + + +bool Profile::enableLocalSaves(bool enable) +{ + if (enable) { + if (m_Directory.exists("_saves")) { + m_Directory.rename("_saves", "saves"); + } else { + m_Directory.mkdir("saves"); + } + } else { + + QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), + tr("Do you want to delete local savegames? (If you select \"No\", the save games " + "will show up again if you re-enable local savegames)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + removeDir(m_Directory.absoluteFilePath("_saves")); + } else if (res == QMessageBox::No) { + m_Directory.rename("saves", "_saves"); + } else { + return false; + } + } + + // default: assume success + return true; +} + + +QString Profile::getModlistFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); +} + +QString Profile::getPluginsFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); +} + + +QString Profile::getLoadOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); +} + + +QString Profile::getLockedOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); +} + + +QString Profile::getArchivesFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); +} + +QString Profile::getDeleterFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); +} + + +QString Profile::getIniFileName() const +{ + std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); + return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); +} + +QString Profile::getPath() const +{ + return QDir::cleanPath(m_Directory.absolutePath()); +} diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 3aceeb01..cbf50dfc 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -17,258 +17,262 @@ 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 "profilesdialog.h"
-#include "ui_profilesdialog.h"
-#include "profile.h"
-#include "report.h"
-#include "utility.h"
-#include "transfersavesdialog.h"
-#include "profileinputdialog.h"
-#include "mainwindow.h"
-#include <gameinfo.h>
-#include <QListWidgetItem>
-#include <QInputDialog>
-#include <QLineEdit>
-#include <QDirIterator>
-#include <QMessageBox>
-#include <QWhatsThis>
-
-
-ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent)
- : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false)
-{
- ui->setupUi(this);
-
- QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())));
- profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
- m_ProfilesList = findChild<QListWidget*>("profilesList");
-
- QDirIterator profileIter(profilesDir);
-
- while (profileIter.hasNext()) {
- profileIter.next();
- addItem(profileIter.filePath());
- }
-
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
- if (!GameInfo::instance().requiresBSAInvalidation()) {
- invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
- invalidationBox->setEnabled(false);
- }
-}
-
-ProfilesDialog::~ProfilesDialog()
-{
- delete ui;
-}
-
-void ProfilesDialog::showEvent(QShowEvent *event)
-{
- TutorableDialog::showEvent(event);
-
- if (m_ProfilesList->count() == 0) {
- QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0));
- pos.rx() += m_ProfilesList->width() / 2;
- pos.ry() += (m_ProfilesList->height() / 2) - 20;
- QWhatsThis::showText(pos,
- QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. "
- "ATTENTION: Run the game at least once before creating a profile!"), m_ProfilesList);
- }
-}
-
-void ProfilesDialog::on_closeButton_clicked()
-{
- close();
-}
-
-
-void ProfilesDialog::addItem(const QString &name)
-{
- try {
- QVariant temp;
- QDir profileDir(name);
- temp.setValue(Profile(profileDir));
- QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList);
- newItem->setData(Qt::UserRole, temp);
- m_FailState = false;
- } catch (const std::exception& e) {
- reportError(tr("failed to create profile: %1").arg(e.what()));
- }
-}
-
-
-void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings)
-{
- try {
- QVariant temp;
- temp.setValue(Profile(name, useDefaultSettings));
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
- newItem->setData(Qt::UserRole, temp);
- profilesList->addItem(newItem);
- m_FailState = false;
- } catch (const std::exception&) {
- m_FailState = true;
- throw;
- }
-}
-
-
-void ProfilesDialog::createProfile(const QString &name, const Profile &reference)
-{
- try {
- Profile newProfile = Profile::createFrom(name, reference);
-
- QVariant temp;
- temp.setValue(newProfile);
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
- newItem->setData(Qt::UserRole, temp);
- profilesList->addItem(newItem);
- m_FailState = false;
- } catch (const std::exception&) {
- m_FailState = true;
- throw;
- }
-}
-
-
-void ProfilesDialog::on_addProfileButton_clicked()
-{
- ProfileInputDialog dialog(this);
- bool okClicked = dialog.exec();
- QString name = dialog.getName();
-
- if (okClicked && (name.size() > 0)) {
- try {
- createProfile(name, dialog.getPreferDefaultSettings());
- } catch (const std::exception &e) {
- reportError(tr("failed to create profile: %1").arg(e.what()));
- }
- }
-}
-
-void ProfilesDialog::on_copyProfileButton_clicked()
-{
- bool okClicked;
- QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked);
- if (okClicked && (name.size() > 0)) {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
- try {
- const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>();
- createProfile(name, currentProfile);
- } catch (const std::exception &e) {
- reportError(tr("failed to copy profile: %1").arg(e.what()));
- }
- }
-}
-
-void ProfilesDialog::on_removeProfileButton_clicked()
-{
- QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile?"),
- QMessageBox::Yes | QMessageBox::No);
-
- if (confirmBox.exec() == QMessageBox::Yes) {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
- const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>();
-
- // on destruction, the profile object would write the profile.ini file again, so
- // we have to get rid of the it before deleting the directory
- QString profilePath = currentProfile.getPath();
- QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow());
- if (item != NULL) {
- delete item;
- }
- removeDir(profilePath);
- }
-}
-
-
-void ProfilesDialog::on_invalidationBox_stateChanged(int state)
-{
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
- QListWidgetItem *currentItem = profilesList->currentItem();
- if (currentItem == NULL) {
- return;
- }
- if (!ui->invalidationBox->isEnabled()) {
- return;
- }
- try {
- QVariant currentProfileVariant = currentItem->data(Qt::UserRole);
- if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) {
- return;
- }
- const Profile ¤tProfile = currentItem->data(Qt::UserRole).value<Profile>();
- if (state == Qt::Unchecked) {
- currentProfile.deactivateInvalidation();
- } else {
- currentProfile.activateInvalidation(m_GamePath + "/data");
- }
- } catch (const std::exception &e) {
- reportError(tr("failed to change archive invalidation state: %1").arg(e.what()));
- }
-}
-
-
-void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*)
-{
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
- QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox");
- QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton");
- QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton");
- QPushButton *transferButton = findChild<QPushButton*>("transferButton");
-
- if (current != NULL) {
- const Profile ¤tProfile = current->data(Qt::UserRole).value<Profile>();
-
- try {
- bool invalidationSupported = false;
- invalidationBox->blockSignals(true);
- invalidationBox->setChecked(currentProfile.invalidationActive(&invalidationSupported));
- invalidationBox->setEnabled(invalidationSupported);
- invalidationBox->blockSignals(false);
-
- bool localSaves = currentProfile.localSavesEnabled();
- transferButton->setEnabled(localSaves);
- // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves
- // were disabled and delete the files/rename the dir
- localSavesBox->blockSignals(true);
- localSavesBox->setChecked(localSaves);
- localSavesBox->blockSignals(false);
-
- copyButton->setEnabled(true);
- removeButton->setEnabled(true);
- } catch (const std::exception& E) {
- reportError(tr("failed to determine if invalidation is active: %1").arg(E.what()));
- copyButton->setEnabled(false);
- removeButton->setEnabled(false);
- invalidationBox->setChecked(false);
- }
- } else {
- invalidationBox->setChecked(false);
- copyButton->setEnabled(false);
- removeButton->setEnabled(false);
- }
-}
-
-void ProfilesDialog::on_localSavesBox_stateChanged(int state)
-{
- Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>();
- if (currentProfile.enableLocalSaves(state == Qt::Checked)) {
- ui->transferButton->setEnabled(state == Qt::Checked);
- } else {
- // revert checkbox-state
- ui->localSavesBox->setChecked(state != Qt::Checked);
- }
-}
-
-void ProfilesDialog::on_transferButton_clicked()
-{
- const Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>();
- TransferSavesDialog transferDialog(currentProfile, this);
- transferDialog.exec();
-}
+#include "profilesdialog.h" +#include "ui_profilesdialog.h" +#include "profile.h" +#include "report.h" +#include "utility.h" +#include "transfersavesdialog.h" +#include "profileinputdialog.h" +#include "mainwindow.h" +#include <gameinfo.h> +#include <QListWidgetItem> +#include <QInputDialog> +#include <QLineEdit> +#include <QDirIterator> +#include <QMessageBox> +#include <QWhatsThis> + + +using namespace MOBase; +using namespace MOShared; + + +ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) + : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) +{ + ui->setupUi(this); + + QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + m_ProfilesList = findChild<QListWidget*>("profilesList"); + + QDirIterator profileIter(profilesDir); + + while (profileIter.hasNext()) { + profileIter.next(); + addItem(profileIter.filePath()); + } + + QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox"); + if (!GameInfo::instance().requiresBSAInvalidation()) { + invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); + invalidationBox->setEnabled(false); + } +} + +ProfilesDialog::~ProfilesDialog() +{ + delete ui; +} + +void ProfilesDialog::showEvent(QShowEvent *event) +{ + TutorableDialog::showEvent(event); + + if (m_ProfilesList->count() == 0) { + QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0)); + pos.rx() += m_ProfilesList->width() / 2; + pos.ry() += (m_ProfilesList->height() / 2) - 20; + QWhatsThis::showText(pos, + QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. " + "ATTENTION: Run the game at least once before creating a profile!"), m_ProfilesList); + } +} + +void ProfilesDialog::on_closeButton_clicked() +{ + close(); +} + + +void ProfilesDialog::addItem(const QString &name) +{ + try { + QVariant temp; + QDir profileDir(name); + temp.setValue(Profile(profileDir)); + QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); + newItem->setData(Qt::UserRole, temp); + m_FailState = false; + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } +} + + +void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) +{ + try { + QVariant temp; + temp.setValue(Profile(name, useDefaultSettings)); + QListWidget *profilesList = findChild<QListWidget*>("profilesList"); + QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); + newItem->setData(Qt::UserRole, temp); + profilesList->addItem(newItem); + m_FailState = false; + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + + +void ProfilesDialog::createProfile(const QString &name, const Profile &reference) +{ + try { + Profile newProfile = Profile::createFrom(name, reference); + + QVariant temp; + temp.setValue(newProfile); + QListWidget *profilesList = findChild<QListWidget*>("profilesList"); + QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); + newItem->setData(Qt::UserRole, temp); + profilesList->addItem(newItem); + m_FailState = false; + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + + +void ProfilesDialog::on_addProfileButton_clicked() +{ + ProfileInputDialog dialog(this); + bool okClicked = dialog.exec(); + QString name = dialog.getName(); + + if (okClicked && (name.size() > 0)) { + try { + createProfile(name, dialog.getPreferDefaultSettings()); + } catch (const std::exception &e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } + } +} + +void ProfilesDialog::on_copyProfileButton_clicked() +{ + bool okClicked; + QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked); + if (okClicked && (name.size() > 0)) { + QListWidget *profilesList = findChild<QListWidget*>("profilesList"); + + try { + const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + createProfile(name, currentProfile); + } catch (const std::exception &e) { + reportError(tr("failed to copy profile: %1").arg(e.what())); + } + } +} + +void ProfilesDialog::on_removeProfileButton_clicked() +{ + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile?"), + QMessageBox::Yes | QMessageBox::No); + + if (confirmBox.exec() == QMessageBox::Yes) { + QListWidget *profilesList = findChild<QListWidget*>("profilesList"); + + const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + + // on destruction, the profile object would write the profile.ini file again, so + // we have to get rid of the it before deleting the directory + QString profilePath = currentProfile.getPath(); + QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); + if (item != NULL) { + delete item; + } + removeDir(profilePath); + } +} + + +void ProfilesDialog::on_invalidationBox_stateChanged(int state) +{ + QListWidget *profilesList = findChild<QListWidget*>("profilesList"); + + QListWidgetItem *currentItem = profilesList->currentItem(); + if (currentItem == NULL) { + return; + } + if (!ui->invalidationBox->isEnabled()) { + return; + } + try { + QVariant currentProfileVariant = currentItem->data(Qt::UserRole); + if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { + return; + } + const Profile ¤tProfile = currentItem->data(Qt::UserRole).value<Profile>(); + if (state == Qt::Unchecked) { + currentProfile.deactivateInvalidation(); + } else { + currentProfile.activateInvalidation(m_GamePath + "/data"); + } + } catch (const std::exception &e) { + reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); + } +} + + +void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) +{ + QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox"); + QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox"); + QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton"); + QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton"); + QPushButton *transferButton = findChild<QPushButton*>("transferButton"); + + if (current != NULL) { + const Profile ¤tProfile = current->data(Qt::UserRole).value<Profile>(); + + try { + bool invalidationSupported = false; + invalidationBox->blockSignals(true); + invalidationBox->setChecked(currentProfile.invalidationActive(&invalidationSupported)); + invalidationBox->setEnabled(invalidationSupported); + invalidationBox->blockSignals(false); + + bool localSaves = currentProfile.localSavesEnabled(); + transferButton->setEnabled(localSaves); + // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves + // were disabled and delete the files/rename the dir + localSavesBox->blockSignals(true); + localSavesBox->setChecked(localSaves); + localSavesBox->blockSignals(false); + + copyButton->setEnabled(true); + removeButton->setEnabled(true); + } catch (const std::exception& E) { + reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); + copyButton->setEnabled(false); + removeButton->setEnabled(false); + invalidationBox->setChecked(false); + } + } else { + invalidationBox->setChecked(false); + copyButton->setEnabled(false); + removeButton->setEnabled(false); + } +} + +void ProfilesDialog::on_localSavesBox_stateChanged(int state) +{ + Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + if (currentProfile.enableLocalSaves(state == Qt::Checked)) { + ui->transferButton->setEnabled(state == Qt::Checked); + } else { + // revert checkbox-state + ui->localSavesBox->setChecked(state != Qt::Checked); + } +} + +void ProfilesDialog::on_transferButton_clicked() +{ + const Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + TransferSavesDialog transferDialog(currentProfile, this); + transferDialog.exec(); +} diff --git a/src/profilesdialog.h b/src/profilesdialog.h index c05340c9..df08aa45 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -17,78 +17,78 @@ 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 PROFILESDIALOG_H
-#define PROFILESDIALOG_H
-
-#include "tutorabledialog.h"
-#include <QListWidgetItem>
-#include "profile.h"
-
-
-namespace Ui {
- class ProfilesDialog;
-}
-
-
-/**
- * @brief Dialog that can be used to create/delete/modify profiles
- **/
-class ProfilesDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- * @param gamePath the path to the game directory
- * @param parent parent widget
- * @todo the game path could be retrieved from GameInfo just as easily
- **/
- explicit ProfilesDialog(const QString &gamePath, QWidget *parent = 0);
- ~ProfilesDialog();
-
- /**
- * @return true if creation of a new profile failed
- * @todo the notion of a fail state makes little sense in the current dialog
- **/
- bool failed() const { return m_FailState; }
-
-protected:
-
- virtual void showEvent(QShowEvent *event);
-
-private:
-
- void addItem(const QString &name);
- void createProfile(const QString &name, bool useDefaultSettings);
- void createProfile(const QString &name, const Profile &reference);
-
-private slots:
-
- void on_closeButton_clicked();
-
- void on_addProfileButton_clicked();
-
- void on_invalidationBox_stateChanged(int arg1);
-
- void on_copyProfileButton_clicked();
-
- void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
-
- void on_removeProfileButton_clicked();
-
- void on_localSavesBox_stateChanged(int arg1);
-
- void on_transferButton_clicked();
-
-private:
- Ui::ProfilesDialog *ui;
- QString m_GamePath;
- QListWidget *m_ProfilesList;
- bool m_FailState;
-
-};
-
-#endif // PROFILESDIALOG_H
+#ifndef PROFILESDIALOG_H +#define PROFILESDIALOG_H + +#include "tutorabledialog.h" +#include <QListWidgetItem> +#include "profile.h" + + +namespace Ui { + class ProfilesDialog; +} + + +/** + * @brief Dialog that can be used to create/delete/modify profiles + **/ +class ProfilesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param gamePath the path to the game directory + * @param parent parent widget + * @todo the game path could be retrieved from GameInfo just as easily + **/ + explicit ProfilesDialog(const QString &gamePath, QWidget *parent = 0); + ~ProfilesDialog(); + + /** + * @return true if creation of a new profile failed + * @todo the notion of a fail state makes little sense in the current dialog + **/ + bool failed() const { return m_FailState; } + +protected: + + virtual void showEvent(QShowEvent *event); + +private: + + void addItem(const QString &name); + void createProfile(const QString &name, bool useDefaultSettings); + void createProfile(const QString &name, const Profile &reference); + +private slots: + + void on_closeButton_clicked(); + + void on_addProfileButton_clicked(); + + void on_invalidationBox_stateChanged(int arg1); + + void on_copyProfileButton_clicked(); + + void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + + void on_removeProfileButton_clicked(); + + void on_localSavesBox_stateChanged(int arg1); + + void on_transferButton_clicked(); + +private: + Ui::ProfilesDialog *ui; + QString m_GamePath; + QListWidget *m_ProfilesList; + bool m_FailState; + +}; + +#endif // PROFILESDIALOG_H diff --git a/src/report.cpp b/src/report.cpp index 6b379a1f..ca7e1dc9 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -17,33 +17,37 @@ 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 "report.h"
-#include "utility.h"
-#include <QMessageBox>
-#include <QApplication>
-#include <Windows.h>
-
-void reportError(QString message)
-{
- if (QApplication::topLevelWidgets().count() != 0) {
- QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok);
- messageBox.exec();
- } else {
- ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK);
- }
-}
-
-
-std::tstring toTString(const QString& source)
-{
-#ifdef UNICODE
- wchar_t* temp = new wchar_t[source.size() + 1];
- source.toWCharArray(temp);
- temp[source.size()] = '\0';
- std::tstring result(temp);
- delete[] temp;
- return result;
-#else // UNICODE
- return source.toAscii();
-#endif // UNICODE
-}
+#include "report.h" +#include "utility.h" +#include <QMessageBox> +#include <QApplication> +#include <Windows.h> + + +using namespace MOBase; + + +void reportError(QString message) +{ + if (QApplication::topLevelWidgets().count() != 0) { + QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok); + messageBox.exec(); + } else { + ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK); + } +} + + +std::tstring toTString(const QString& source) +{ +#ifdef UNICODE + wchar_t* temp = new wchar_t[source.size() + 1]; + source.toWCharArray(temp); + temp[source.size()] = '\0'; + std::tstring result(temp); + delete[] temp; + return result; +#else // UNICODE + return source.toAscii(); +#endif // UNICODE +} diff --git a/src/savegamegamebryo.cpp b/src/savegamegamebryo.cpp index 3b7ef017..f3794347 100644 --- a/src/savegamegamebryo.cpp +++ b/src/savegamegamebryo.cpp @@ -17,328 +17,331 @@ 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 "savegamegamebyro.h"
-#include <QFile>
-#include <QBuffer>
-#include <set>
-#include "gameinfo.h"
-#include <QFileInfo>
-#include <QDateTime>
-#include <limits>
-
-
-template <typename T>
-void FileRead(QFile &file, T &value)
-{
- int read = file.read(reinterpret_cast<char*>(&value), sizeof(T));
- if (read != sizeof(T)) {
- throw std::runtime_error("unexpected end of file");
- }
-}
-
-
-template <typename T>
-void FileSkip(QFile &file, int count = 1)
-{
- char ignore[sizeof(T)];
- for (int i = 0; i < count; ++i) {
- if (file.read(ignore, sizeof(T)) != sizeof(T)) {
- throw std::runtime_error("unexpected end of file");
- }
- }
-}
-
-
-QString ReadBString(QFile &file)
-{
- char buffer[256];
- file.read(buffer, 1); // size including zero termination
- unsigned char size = buffer[0];
- file.read(buffer, size);
- return QString::fromLatin1(buffer, size);
-}
-
-
-QString ReadFOSString(QFile &file, bool delimiter)
-{
- union {
- char lengthBuffer[2];
- unsigned short length;
- };
-
- file.read(lengthBuffer, 2);
- if (delimiter) {
- FileSkip<char>(file); // 0x7c
- }
- char *buffer = new char[length];
- file.read(buffer, length);
-
- QString result = QString::fromLatin1(buffer, length);
- delete [] buffer;
-
- return result;
-}
-
-
-SaveGameGamebryo::SaveGameGamebryo(QObject *parent)
- : SaveGame(parent), m_Plugins()
-{
-}
-
-
-SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName)
- : SaveGame(parent, fileName), m_Plugins()
-{
- readFile(fileName);
-}
-
-
-SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference)
- : SaveGame(reference), m_Plugins(reference.m_Plugins)
-{
-}
-
-
-SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference)
-{
- if (&reference != this) {
- SaveGame::operator =(reference);
- m_Plugins = reference.m_Plugins;
- }
- return *this;
-}
-
-
-SaveGameGamebryo::~SaveGameGamebryo()
-{
-}
-
-
-QStringList SaveGameGamebryo::attachedFiles() const
-{
- QStringList result;
- QString seFileFile = fileName().mid(0).replace(".ess", ".skse");
- QFileInfo seFile(seFileFile);
- if (seFile.exists()) {
- result.append(seFile.absoluteFilePath());
- }
- return result;
-}
-
-
-void SaveGameGamebryo::readSkyrimFile(QFile &saveFile)
-{
- char fileID[14];
-
- saveFile.read(fileID, 13);
- fileID[13] = '\0';
- if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) {
- throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
- }
-
- FileSkip<unsigned long>(saveFile); // header size
- FileSkip<unsigned long>(saveFile); // header version, -> 8
- FileRead(saveFile, m_SaveNumber);
-
- m_PCName = ReadFOSString(saveFile, false);
-
- unsigned long temp;
- FileRead(saveFile, temp); // player level
- m_PCLevel = static_cast<unsigned short>(temp);
-
- m_PCLocation = ReadFOSString(saveFile, false);
- ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss
- ReadFOSString(saveFile, false); // race name (i.e. BretonRace)
-
-
- FileSkip<unsigned short>(saveFile); // ???
- FileSkip<float>(saveFile, 2); // ???
- FileSkip<unsigned char>(saveFile, 8); // filetime
-
-// FileSkip<unsigned char>(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe
-
- unsigned long width, height;
- FileRead(saveFile, width); // 320
- FileRead(saveFile, height); // 192
-
- QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
- saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
- // why do I have to copy here? without the copy, the buffer seems to get deleted after the
- // temporary vanishes, but Qts implicit sharing should handle that?
- m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
-
- FileSkip<unsigned char>(saveFile); // form version
- FileSkip<unsigned long>(saveFile); // plugin info size
-
- unsigned char pluginCount;
- FileRead(saveFile, pluginCount);
-
- for (int i = 0; i < pluginCount; ++i) {
- m_Plugins.push_back(ReadFOSString(saveFile, false));
- }
-}
-
-
-void SaveGameGamebryo::readESSFile(QFile &saveFile)
-{
- char fileID[13];
- unsigned char versionMinor;
- unsigned long headerVersion, saveHeaderSize;
-// *** format is different for fallout!
- saveFile.read(fileID, 12);
- fileID[12] = '\0';
- FileSkip<unsigned char>(saveFile); FileRead(saveFile, versionMinor);
- FileSkip<SYSTEMTIME>(saveFile); // modified time
- FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize);
-
- if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) {
- throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
- }
-
- FileRead(saveFile, m_SaveNumber);
-
- m_PCName = ReadBString(saveFile);
-
- FileRead(saveFile, m_PCLevel);
- m_PCLocation = ReadBString(saveFile);
- FileSkip<float>(saveFile); // game days
- FileSkip<unsigned long>(saveFile); // game ticks
- FileRead(saveFile, m_CreationTime);
-
- unsigned long size;
- FileRead(saveFile, size); // screenshot size
-
- unsigned long width, height;
- FileRead(saveFile, width); FileRead(saveFile, height);
- QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
- saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
- // why do I have to copy here? without the copy, the buffer seems to get deleted after the
- // temporary vanishes, but Qts implicit sharing should handle that?
- m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
-
- unsigned char pluginCount;
- FileRead(saveFile, pluginCount);
-
- for (int i = 0; i < pluginCount; ++i) {
- QString name = ReadBString(saveFile);
- m_Plugins.push_back(name);
- }
-}
-
-
-void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas)
-{
- char fileID[13];
- saveFile.read(fileID, 12);
- // the signature is only 11 characters, the 12th is random?
- fileID[11] = '\0';
-
- if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) {
- throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
- }
-
- char ignore = 0x00;
- while (ignore != 0x7c) {
- FileRead<char>(saveFile, ignore); // unknown
- }
- if (newVegas) {
- ignore = 0x00;
- // in new vegas there is another block of uninteresting (?) information
- FileSkip<char>(saveFile); // 0x7c
- while (ignore != 0x7c) {
- FileRead<char>(saveFile, ignore); // unknown
- }
- }
-
- unsigned long width, height;
- FileRead(saveFile, width);
- FileSkip<char>(saveFile); // 0x7c
- FileRead(saveFile, height);
- FileSkip<char>(saveFile); // 0x7c
-
- FileRead(saveFile, m_SaveNumber);
- FileSkip<char>(saveFile); // 0x7c
-
- m_PCName = ReadFOSString(saveFile, true);
- FileSkip<char>(saveFile); // 0x7c
-
- ReadFOSString(saveFile, true);
- FileSkip<char>(saveFile); // 0x7c
-
- long Level;
- FileRead(saveFile, Level);
- m_PCLevel = Level;
- FileSkip<char>(saveFile); // 0x7c
-
- m_PCLocation = ReadFOSString(saveFile, true);
- FileSkip<char>(saveFile); // 0x7c
-
- ReadFOSString(saveFile, true); // playtime
-
- FileSkip<char>(saveFile);
-
- QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
- saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
- // why do I have to copy here? without the copy, the buffer seems to get deleted after the
- // temporary vanishes, but Qts implicit sharing should handle that?
- m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256);
-
- FileSkip<char>(saveFile, 5); // unknown
-
- unsigned char pluginCount = 0;
- FileRead(saveFile, pluginCount);
- FileSkip<char>(saveFile); // 0x7c
-
- for (int i = 0; i < pluginCount; ++i) {
- QString name = ReadFOSString(saveFile, true);
- m_Plugins.push_back(name);
- FileSkip<char>(saveFile); // 0x7c
- }
-}
-
-
-void SaveGameGamebryo::setCreationTime(const QString &fileName)
-{
- QFileInfo creationTime(fileName);
- QDateTime modified = creationTime.lastModified();
- memset(&m_CreationTime, 0, sizeof(SYSTEMTIME));
-
- m_CreationTime.wDay = static_cast<WORD>(modified.date().day());
- m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek());
- m_CreationTime.wMonth = static_cast<WORD>(modified.date().month());
- m_CreationTime.wYear =static_cast<WORD>( modified.date().year());
-
- m_CreationTime.wHour = static_cast<WORD>(modified.time().hour());
- m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute());
- m_CreationTime.wSecond = static_cast<WORD>(modified.time().second());
- m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec());
-}
-
-
-void SaveGameGamebryo::readFile(const QString &fileName)
-{
- m_FileName = fileName;
- QFile saveFile(fileName);
- if (!saveFile.open(QIODevice::ReadOnly)) {
- throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData());
- }
- switch (GameInfo::instance().getType()) {
- case GameInfo::TYPE_FALLOUT3: {
- setCreationTime(fileName);
- readFOSFile(saveFile, false);
- } break;
- case GameInfo::TYPE_FALLOUTNV: {
- setCreationTime(fileName);
- readFOSFile(saveFile, true);
- } break;
- case GameInfo::TYPE_OBLIVION: {
- readESSFile(saveFile);
- } break;
- case GameInfo::TYPE_SKYRIM: {
- setCreationTime(fileName);
- readSkyrimFile(saveFile);
- } break;
- }
-
- saveFile.close();
-}
+#include "savegamegamebyro.h" +#include <QFile> +#include <QBuffer> +#include <set> +#include "gameinfo.h" +#include <QFileInfo> +#include <QDateTime> +#include <limits> + + +using namespace MOShared; + + +template <typename T> +void FileRead(QFile &file, T &value) +{ + int read = file.read(reinterpret_cast<char*>(&value), sizeof(T)); + if (read != sizeof(T)) { + throw std::runtime_error("unexpected end of file"); + } +} + + +template <typename T> +void FileSkip(QFile &file, int count = 1) +{ + char ignore[sizeof(T)]; + for (int i = 0; i < count; ++i) { + if (file.read(ignore, sizeof(T)) != sizeof(T)) { + throw std::runtime_error("unexpected end of file"); + } + } +} + + +QString ReadBString(QFile &file) +{ + char buffer[256]; + file.read(buffer, 1); // size including zero termination + unsigned char size = buffer[0]; + file.read(buffer, size); + return QString::fromLatin1(buffer, size); +} + + +QString ReadFOSString(QFile &file, bool delimiter) +{ + union { + char lengthBuffer[2]; + unsigned short length; + }; + + file.read(lengthBuffer, 2); + if (delimiter) { + FileSkip<char>(file); // 0x7c + } + char *buffer = new char[length]; + file.read(buffer, length); + + QString result = QString::fromLatin1(buffer, length); + delete [] buffer; + + return result; +} + + +SaveGameGamebryo::SaveGameGamebryo(QObject *parent) + : SaveGame(parent), m_Plugins() +{ +} + + +SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName) + : SaveGame(parent, fileName), m_Plugins() +{ + readFile(fileName); +} + + +SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference) + : SaveGame(reference), m_Plugins(reference.m_Plugins) +{ +} + + +SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference) +{ + if (&reference != this) { + SaveGame::operator =(reference); + m_Plugins = reference.m_Plugins; + } + return *this; +} + + +SaveGameGamebryo::~SaveGameGamebryo() +{ +} + + +QStringList SaveGameGamebryo::attachedFiles() const +{ + QStringList result; + QString seFileFile = fileName().mid(0).replace(".ess", ".skse"); + QFileInfo seFile(seFileFile); + if (seFile.exists()) { + result.append(seFile.absoluteFilePath()); + } + return result; +} + + +void SaveGameGamebryo::readSkyrimFile(QFile &saveFile) +{ + char fileID[14]; + + saveFile.read(fileID, 13); + fileID[13] = '\0'; + if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + FileSkip<unsigned long>(saveFile); // header size + FileSkip<unsigned long>(saveFile); // header version, -> 8 + FileRead(saveFile, m_SaveNumber); + + m_PCName = ReadFOSString(saveFile, false); + + unsigned long temp; + FileRead(saveFile, temp); // player level + m_PCLevel = static_cast<unsigned short>(temp); + + m_PCLocation = ReadFOSString(saveFile, false); + ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss + ReadFOSString(saveFile, false); // race name (i.e. BretonRace) + + + FileSkip<unsigned short>(saveFile); // ??? + FileSkip<float>(saveFile, 2); // ??? + FileSkip<unsigned char>(saveFile, 8); // filetime + +// FileSkip<unsigned char>(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe + + unsigned long width, height; + FileRead(saveFile, width); // 320 + FileRead(saveFile, height); // 192 + + QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); + + FileSkip<unsigned char>(saveFile); // form version + FileSkip<unsigned long>(saveFile); // plugin info size + + unsigned char pluginCount; + FileRead(saveFile, pluginCount); + + for (int i = 0; i < pluginCount; ++i) { + m_Plugins.push_back(ReadFOSString(saveFile, false)); + } +} + + +void SaveGameGamebryo::readESSFile(QFile &saveFile) +{ + char fileID[13]; + unsigned char versionMinor; + unsigned long headerVersion, saveHeaderSize; +// *** format is different for fallout! + saveFile.read(fileID, 12); + fileID[12] = '\0'; + FileSkip<unsigned char>(saveFile); FileRead(saveFile, versionMinor); + FileSkip<SYSTEMTIME>(saveFile); // modified time + FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize); + + if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + FileRead(saveFile, m_SaveNumber); + + m_PCName = ReadBString(saveFile); + + FileRead(saveFile, m_PCLevel); + m_PCLocation = ReadBString(saveFile); + FileSkip<float>(saveFile); // game days + FileSkip<unsigned long>(saveFile); // game ticks + FileRead(saveFile, m_CreationTime); + + unsigned long size; + FileRead(saveFile, size); // screenshot size + + unsigned long width, height; + FileRead(saveFile, width); FileRead(saveFile, height); + QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); + + unsigned char pluginCount; + FileRead(saveFile, pluginCount); + + for (int i = 0; i < pluginCount; ++i) { + QString name = ReadBString(saveFile); + m_Plugins.push_back(name); + } +} + + +void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas) +{ + char fileID[13]; + saveFile.read(fileID, 12); + // the signature is only 11 characters, the 12th is random? + fileID[11] = '\0'; + + if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + char ignore = 0x00; + while (ignore != 0x7c) { + FileRead<char>(saveFile, ignore); // unknown + } + if (newVegas) { + ignore = 0x00; + // in new vegas there is another block of uninteresting (?) information + FileSkip<char>(saveFile); // 0x7c + while (ignore != 0x7c) { + FileRead<char>(saveFile, ignore); // unknown + } + } + + unsigned long width, height; + FileRead(saveFile, width); + FileSkip<char>(saveFile); // 0x7c + FileRead(saveFile, height); + FileSkip<char>(saveFile); // 0x7c + + FileRead(saveFile, m_SaveNumber); + FileSkip<char>(saveFile); // 0x7c + + m_PCName = ReadFOSString(saveFile, true); + FileSkip<char>(saveFile); // 0x7c + + ReadFOSString(saveFile, true); + FileSkip<char>(saveFile); // 0x7c + + long Level; + FileRead(saveFile, Level); + m_PCLevel = Level; + FileSkip<char>(saveFile); // 0x7c + + m_PCLocation = ReadFOSString(saveFile, true); + FileSkip<char>(saveFile); // 0x7c + + ReadFOSString(saveFile, true); // playtime + + FileSkip<char>(saveFile); + + QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256); + + FileSkip<char>(saveFile, 5); // unknown + + unsigned char pluginCount = 0; + FileRead(saveFile, pluginCount); + FileSkip<char>(saveFile); // 0x7c + + for (int i = 0; i < pluginCount; ++i) { + QString name = ReadFOSString(saveFile, true); + m_Plugins.push_back(name); + FileSkip<char>(saveFile); // 0x7c + } +} + + +void SaveGameGamebryo::setCreationTime(const QString &fileName) +{ + QFileInfo creationTime(fileName); + QDateTime modified = creationTime.lastModified(); + memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); + + m_CreationTime.wDay = static_cast<WORD>(modified.date().day()); + m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek()); + m_CreationTime.wMonth = static_cast<WORD>(modified.date().month()); + m_CreationTime.wYear =static_cast<WORD>( modified.date().year()); + + m_CreationTime.wHour = static_cast<WORD>(modified.time().hour()); + m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute()); + m_CreationTime.wSecond = static_cast<WORD>(modified.time().second()); + m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec()); +} + + +void SaveGameGamebryo::readFile(const QString &fileName) +{ + m_FileName = fileName; + QFile saveFile(fileName); + if (!saveFile.open(QIODevice::ReadOnly)) { + throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData()); + } + switch (GameInfo::instance().getType()) { + case GameInfo::TYPE_FALLOUT3: { + setCreationTime(fileName); + readFOSFile(saveFile, false); + } break; + case GameInfo::TYPE_FALLOUTNV: { + setCreationTime(fileName); + readFOSFile(saveFile, true); + } break; + case GameInfo::TYPE_OBLIVION: { + readESSFile(saveFile); + } break; + case GameInfo::TYPE_SKYRIM: { + setCreationTime(fileName); + readSkyrimFile(saveFile); + } break; + } + + saveFile.close(); +} diff --git a/src/savegameinfowidget.cpp b/src/savegameinfowidget.cpp index 34a0f358..f07a9694 100644 --- a/src/savegameinfowidget.cpp +++ b/src/savegameinfowidget.cpp @@ -17,46 +17,46 @@ 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 "savegameinfowidget.h"
-#include "ui_savegameinfowidget.h"
-#include "utility.h"
-#include <QGraphicsDropShadowEffect>
-
-
-SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent)
- : QWidget(parent), ui(new Ui::SaveGameInfoWidget)
-{
- ui->setupUi(this);
- this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget);
- setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0));
-// installEventFilter(this);
-}
-
-SaveGameInfoWidget::~SaveGameInfoWidget()
-{
- delete ui;
-}
-
-
-void SaveGameInfoWidget::setSave(const SaveGame *saveGame)
-{
- ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber()));
- ui->characterLabel->setText(saveGame->pcName());
- ui->locationLabel->setText(saveGame->pcLocation());
- ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel()));
- ui->dateLabel->setText(ToString(saveGame->creationTime()));
- ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot()));
- if (ui->gameFrame->layout() != NULL) {
- QLayoutItem *item = NULL;
- while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) {
- delete item->widget();
- delete item;
- }
- ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize);
- }
-}
-
-QFrame *SaveGameInfoWidget::getGameFrame()
-{
- return ui->gameFrame;
-}
+#include "savegameinfowidget.h" +#include "ui_savegameinfowidget.h" +#include "utility.h" +#include <QGraphicsDropShadowEffect> + + +SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent) + : QWidget(parent), ui(new Ui::SaveGameInfoWidget) +{ + ui->setupUi(this); + this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget); + setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0)); +// installEventFilter(this); +} + +SaveGameInfoWidget::~SaveGameInfoWidget() +{ + delete ui; +} + + +void SaveGameInfoWidget::setSave(const SaveGame *saveGame) +{ + ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber())); + ui->characterLabel->setText(saveGame->pcName()); + ui->locationLabel->setText(saveGame->pcLocation()); + ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel())); + ui->dateLabel->setText(MOBase::ToString(saveGame->creationTime())); + ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot())); + if (ui->gameFrame->layout() != NULL) { + QLayoutItem *item = NULL; + while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) { + delete item->widget(); + delete item; + } + ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize); + } +} + +QFrame *SaveGameInfoWidget::getGameFrame() +{ + return ui->gameFrame; +} diff --git a/src/savetextasdialog.cpp b/src/savetextasdialog.cpp index 465e3d65..1f23356f 100644 --- a/src/savetextasdialog.cpp +++ b/src/savetextasdialog.cpp @@ -37,7 +37,7 @@ void SaveTextAsDialog::on_saveAsBtn_clicked() if (!fileName.isEmpty()) {
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
- reportError(tr("failed to open \"%1\" for writing").arg(fileName));
+ MOBase::reportError(tr("failed to open \"%1\" for writing").arg(fileName));
return;
}
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 884f9647..503c2596 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -17,439 +17,443 @@ 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 "selfupdater.h"
-#include "utility.h"
-#include "installationmanager.h"
-#include "report.h"
-#include "messagedialog.h"
-#include "downloadmanager.h"
-
-#include <versioninfo.h>
-#include <gameinfo.h>
-#include <skyriminfo.h>
-#include <QMessageBox>
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QDir>
-#include <QLibrary>
-#include <QProcess>
-#include <QApplication>
-#include <util.h>
-
-
-typedef Archive* (*CreateArchiveType)();
-
-
-template <typename T> T resolveFunction(QLibrary &lib, const char *name)
-{
- T temp = reinterpret_cast<T>(lib.resolve(name));
- if (temp == NULL) {
- throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
- }
- return temp;
-}
-
-
-SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent)
- : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1),
- m_Reply(NULL), m_Progress(parent), m_Attempts(3)
-{
- m_Progress.setMaximum(100);
-
- QLibrary archiveLib("dlls\\archive.dll");
- if (!archiveLib.load()) {
- throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
- }
-
- CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
-
- m_CurrentArchive = CreateArchiveFunc();
- if (!m_CurrentArchive->isValid()) {
- throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError()));
- }
-
- connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel()));
-
- VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath()));
-
- m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16,
- version.dwFileVersionMS & 0xFFFF,
- version.dwFileVersionLS >> 16);
-}
-
-
-SelfUpdater::~SelfUpdater()
-{
- delete m_CurrentArchive;
-}
-
-
-void SelfUpdater::testForUpdate()
-{
-/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- emit updateAvailable();
- return;
- }*/
-
- if (m_UpdateRequestID == -1) {
- m_UpdateRequestID = m_Interface->requestDescription(
- SkyrimInfo::getNexusModIDStatic(), this, QVariant(),
- ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
- }
-}
-
-void SelfUpdater::startUpdate()
-{
-/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z");
- installUpdate();
- return;
- }*/
-
- if ((m_UpdateRequestID == -1) &&
- (!m_NewestVersion.isEmpty())) {
- if (QMessageBox::question(m_Parent, tr("Update"),
- tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(),
- this, m_NewestVersion,
- ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
- }
- }
-}
-
-
-void SelfUpdater::download(const QString &downloadLink, const QString &fileName)
-{
- qDebug("download: %s", downloadLink.toUtf8().constData());
- QNetworkAccessManager *accessManager = m_Interface->getAccessManager();
- QUrl dlUrl(downloadLink);
- QNetworkRequest request(dlUrl);
- m_Canceled = false;
- m_Reply = accessManager->get(request);
- m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName)));
- m_UpdateFile.open(QIODevice::WriteOnly);
- m_Progress.setModal(true);
- m_Progress.show();
- m_Progress.setValue(0);
- m_Progress.setWindowTitle(tr("Update"));
- m_Progress.setLabelText(tr("Download in progress"));
-
- connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
- connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
- connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
-}
-
-
-void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
- if (m_Reply != NULL) {
- if (m_Canceled) {
- m_Reply->abort();
- } else {
- if (bytesTotal != 0) {
- m_Progress.setValue((bytesReceived * 100) / bytesTotal);
- }
- }
- }
-}
-
-
-void SelfUpdater::downloadReadyRead()
-{
- if (m_Reply != NULL) {
- m_UpdateFile.write(m_Reply->readAll());
- }
-}
-
-
-void SelfUpdater::downloadFinished()
-{
- int error = QNetworkReply::NoError;
-
- if (m_Reply != NULL) {
- m_UpdateFile.write(m_Reply->readAll());
-
- error = m_Reply->error();
-
- if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) {
- m_Canceled = true;
- }
-
- m_Progress.hide();
- m_Reply->close();
- m_Reply->deleteLater();
- m_Reply = NULL;
- }
-
- m_UpdateFile.close();
-
- if ((m_UpdateFile.size() == 0) ||
- (error != QNetworkReply::NoError) ||
- m_Canceled) {
- if (!m_Canceled) {
- reportError(tr("Download failed: %1").arg(error));
- }
- m_UpdateFile.remove();
- return;
- }
-
- qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData());
-
- try {
- installUpdate();
- } catch (const std::exception &e) {
- reportError(tr("Failed to install update: %1").arg(e.what()));
- }
-}
-
-
-void SelfUpdater::downloadCancel()
-{
- m_Canceled = true;
-}
-
-
-void SelfUpdater::installUpdate()
-{
- const QString mopath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()));
-
- QString backupPath = mopath.mid(0).append("/update_backup");
- QDir().mkdir(backupPath);
-
- // rename files that are currently open so we can unpack the update
- if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(),
- new MethodCallback<SelfUpdater, void, LPSTR>(this, &SelfUpdater::queryPassword))) {
- throw MyException(tr("failed to open archive \"%1\": %2")
- .arg(m_UpdateFile.fileName())
- .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError())));
- }
-
- // move all files contained in the archive out of the way,
- // otherwise we can't overwrite everything
- FileData* const *data;
- size_t size;
- m_CurrentArchive->getFileList(data, size);
-
- for (size_t i = 0; i < size; ++i) {
- QString outputName = ToQString(data[i]->getFileName());
- if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) {
- outputName = outputName.mid(13);
- data[i]->setOutputFileName(ToWString(outputName).c_str());
- } else if (outputName == "ModOrganizer") {
- data[i]->setSkip(true);
- }
- QFile file(mopath.mid(0).append("/").append(outputName));
- if (file.exists()) {
- file.rename(backupPath.mid(0).append("/").append(outputName));
- }
- }
-
- // now unpack the archive into the mo directory
- if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(),
- new MethodCallback<SelfUpdater, void, float>(this, &SelfUpdater::updateProgress),
- new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::updateProgressFile),
- new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::report7ZipError))) {
- throw std::runtime_error("extracting failed");
- }
-
- m_CurrentArchive->close();
-
- m_UpdateFile.remove();
-
- QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted."));
-
- QProcess newProcess;
- if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) {
- newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update"));
- } else {
- newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update"));
- }
- emit restart();
-}
-
-void SelfUpdater::queryPassword(LPSTR)
-{
- // nop
-}
-
-void SelfUpdater::updateProgress(float)
-{
- // nop
-}
-
-void SelfUpdater::updateProgressFile(LPCWSTR)
-{
- // nop
-}
-
-void SelfUpdater::report7ZipError(LPCWSTR errorMessage)
-{
- QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage));
-}
-
-
-QString SelfUpdater::retrieveNews(const QString &description)
-{
- QStringList temp = description.split("[s][/s]");
- if (temp.length() < 2) {
- return QString();
- } else {
- return temp.at(1);
- }
-}
-
-
-void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
-
- QVariantMap result = resultData.toMap();
- QString motd = retrieveNews(result["description"].toString()).trimmed();
- if (motd.length() != 0) {
- emit motdAvailable(motd);
- }
-
- m_NewestVersion = result["version"].toString();
- if (m_NewestVersion.isEmpty()) {
- QTimer::singleShot(5000, this, SLOT(testForUpdate()));
- }
-
- VersionInfo currentVersion(m_MOVersion);
- VersionInfo newestVersion(m_NewestVersion);
-
- if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) {
- emit updateAvailable();
- } else if (newestVersion < currentVersion) {
- qDebug("this version is newer than the current version on nexus (%s vs %s)",
- currentVersion.canonicalString().toUtf8().constData(),
- newestVersion.canonicalString().toUtf8().constData());
- }
- }
-}
-
-
-void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID)
-{
- if (requestID != m_UpdateRequestID) {
- return;
- }
- QString version = userData.toString();
-
- m_UpdateRequestID = -1;
-
- if (!resultData.canConvert<QVariantList>()) {
- qCritical("invalid files result: %s", resultData.toString().toUtf8().constData());
- reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log."));
- return;
- }
-
- QVariantList result = resultData.toList();
-
- QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version));
- QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version));
- int updateFileID = -1;
- QString updateFileName;
- int mainFileID = -1;
- QString mainFileName;
- int mainFileSize = 0;
-
- foreach(QVariant file, result) {
- QVariantMap fileInfo = file.toMap();
- if (!fileInfo["uri"].toString().endsWith(".7z")) {
- continue;
- }
-
- if (fileInfo["version"].toString() == version) {
- if (fileInfo["category_id"].toInt() == 2) {
- QString description = fileInfo["description"].toString();
- // update
- if (updateExpList.indexIn(description) != -1) {
- // there is an update for the newest version of MO, but does
- // it apply to the current version?
- QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts);
- if (supportedVersions.contains(m_MOVersion.canonicalString())) {
- updateFileID = fileInfo["id"].toInt();
- updateFileName = fileInfo["uri"].toString();
- } else {
- qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
- }
- } else if (updateExpRange.indexIn(description) != -1) {
- VersionInfo rangeLowEnd(updateExpRange.cap(1));
- VersionInfo rangeHighEnd(updateExpRange.cap(2));
- if ((rangeLowEnd <= m_MOVersion) &&
- (m_MOVersion <= rangeHighEnd)) {
- updateFileID = fileInfo["id"].toInt();
- updateFileName = fileInfo["uri"].toString();
- break;
- } else {
- qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
- }
- } else {
- qWarning("invalid update description: %s",
- description.toUtf8().constData());
- }
- } else if (fileInfo["category_id"].toInt() == 1) {
- mainFileID = fileInfo["id"].toInt();
- mainFileName = fileInfo["uri"].toString();
- mainFileSize = fileInfo["size"].toInt();
- }
- }
- }
-
- if (updateFileID != -1) {
- qDebug("update available: %d", updateFileID);
- m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(),
- updateFileID, this, updateFileName,
- ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
- } else if (mainFileID != -1) {
- qDebug("full download required: %d", mainFileID);
- if (QMessageBox::question(m_Parent, tr("Update"),
- tr("No incremental update available for this version, "
- "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
- m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(),
- mainFileID, this, mainFileName,
- ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
- }
- } else {
- qCritical("no file for update found");
- MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent);
- m_Progress.hide();
- }
-}
-
-
-void SelfUpdater::nxmRequestFailed(int, QVariant, int requestID, const QString &errorMessage)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
- if (m_Attempts > 0) {
- QTimer::singleShot(60000, this, SLOT(testForUpdate()));
- --m_Attempts;
- } else {
- MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent);
- }
- }
-}
-
-
-void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID)
-{
- if (requestID == m_UpdateRequestID) {
- m_UpdateRequestID = -1;
- QVariantList serverList = resultData.toList();
- if (serverList.count() != 0) {
- qSort(serverList.begin(), serverList.end(), DownloadManager::ServerByPreference);
-
- QVariantMap dlServer = serverList.first().toMap();
-
- download(dlServer["URI"].toString(), userData.toString());
- } else {
- MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent);
- m_Progress.hide();
- }
- }
-}
-
+#include "selfupdater.h" +#include "utility.h" +#include "installationmanager.h" +#include "report.h" +#include "messagedialog.h" +#include "downloadmanager.h" + +#include <versioninfo.h> +#include <gameinfo.h> +#include <skyriminfo.h> +#include <QMessageBox> +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QDir> +#include <QLibrary> +#include <QProcess> +#include <QApplication> +#include <util.h> + + +using namespace MOBase; +using namespace MOShared; + + +typedef Archive* (*CreateArchiveType)(); + + +template <typename T> T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast<T>(lib.resolve(name)); + if (temp == NULL) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); + } + return temp; +} + + +SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) + : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), + m_Reply(NULL), m_Progress(parent), m_Attempts(3) +{ + m_Progress.setMaximum(100); + + QLibrary archiveLib("dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); + } + + CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive"); + + m_CurrentArchive = CreateArchiveFunc(); + if (!m_CurrentArchive->isValid()) { + throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError())); + } + + connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); + + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16); +} + + +SelfUpdater::~SelfUpdater() +{ + delete m_CurrentArchive; +} + + +void SelfUpdater::testForUpdate() +{ +/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + emit updateAvailable(); + return; + }*/ + + if (m_UpdateRequestID == -1) { + m_UpdateRequestID = m_Interface->requestDescription( + SkyrimInfo::getNexusModIDStatic(), this, QVariant(), + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } +} + +void SelfUpdater::startUpdate() +{ +/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z"); + installUpdate(); + return; + }*/ + + if ((m_UpdateRequestID == -1) && + (!m_NewestVersion.isEmpty())) { + if (QMessageBox::question(m_Parent, tr("Update"), + tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(), + this, m_NewestVersion, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } +} + + +void SelfUpdater::download(const QString &downloadLink, const QString &fileName) +{ + qDebug("download: %s", downloadLink.toUtf8().constData()); + QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); + QUrl dlUrl(downloadLink); + QNetworkRequest request(dlUrl); + m_Canceled = false; + m_Reply = accessManager->get(request); + m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName))); + m_UpdateFile.open(QIODevice::WriteOnly); + m_Progress.setModal(true); + m_Progress.show(); + m_Progress.setValue(0); + m_Progress.setWindowTitle(tr("Update")); + m_Progress.setLabelText(tr("Download in progress")); + + connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); + connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); +} + + +void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + if (m_Reply != NULL) { + if (m_Canceled) { + m_Reply->abort(); + } else { + if (bytesTotal != 0) { + m_Progress.setValue((bytesReceived * 100) / bytesTotal); + } + } + } +} + + +void SelfUpdater::downloadReadyRead() +{ + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + } +} + + +void SelfUpdater::downloadFinished() +{ + int error = QNetworkReply::NoError; + + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + + error = m_Reply->error(); + + if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { + m_Canceled = true; + } + + m_Progress.hide(); + m_Reply->close(); + m_Reply->deleteLater(); + m_Reply = NULL; + } + + m_UpdateFile.close(); + + if ((m_UpdateFile.size() == 0) || + (error != QNetworkReply::NoError) || + m_Canceled) { + if (!m_Canceled) { + reportError(tr("Download failed: %1").arg(error)); + } + m_UpdateFile.remove(); + return; + } + + qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + + try { + installUpdate(); + } catch (const std::exception &e) { + reportError(tr("Failed to install update: %1").arg(e.what())); + } +} + + +void SelfUpdater::downloadCancel() +{ + m_Canceled = true; +} + + +void SelfUpdater::installUpdate() +{ + const QString mopath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())); + + QString backupPath = mopath.mid(0).append("/update_backup"); + QDir().mkdir(backupPath); + + // rename files that are currently open so we can unpack the update + if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(), + new MethodCallback<SelfUpdater, void, LPSTR>(this, &SelfUpdater::queryPassword))) { + throw MyException(tr("failed to open archive \"%1\": %2") + .arg(m_UpdateFile.fileName()) + .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError()))); + } + + // move all files contained in the archive out of the way, + // otherwise we can't overwrite everything + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + for (size_t i = 0; i < size; ++i) { + QString outputName = ToQString(data[i]->getFileName()); + if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) { + outputName = outputName.mid(13); + data[i]->setOutputFileName(ToWString(outputName).c_str()); + } else if (outputName == "ModOrganizer") { + data[i]->setSkip(true); + } + QFile file(mopath.mid(0).append("/").append(outputName)); + if (file.exists()) { + file.rename(backupPath.mid(0).append("/").append(outputName)); + } + } + + // now unpack the archive into the mo directory + if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(), + new MethodCallback<SelfUpdater, void, float>(this, &SelfUpdater::updateProgress), + new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::updateProgressFile), + new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::report7ZipError))) { + throw std::runtime_error("extracting failed"); + } + + m_CurrentArchive->close(); + + m_UpdateFile.remove(); + + QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted.")); + + QProcess newProcess; + if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) { + newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update")); + } else { + newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update")); + } + emit restart(); +} + +void SelfUpdater::queryPassword(LPSTR) +{ + // nop +} + +void SelfUpdater::updateProgress(float) +{ + // nop +} + +void SelfUpdater::updateProgressFile(LPCWSTR) +{ + // nop +} + +void SelfUpdater::report7ZipError(LPCWSTR errorMessage) +{ + QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage)); +} + + +QString SelfUpdater::retrieveNews(const QString &description) +{ + QStringList temp = description.split("[s][/s]"); + if (temp.length() < 2) { + return QString(); + } else { + return temp.at(1); + } +} + + +void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + + QVariantMap result = resultData.toMap(); + QString motd = retrieveNews(result["description"].toString()).trimmed(); + if (motd.length() != 0) { + emit motdAvailable(motd); + } + + m_NewestVersion = result["version"].toString(); + if (m_NewestVersion.isEmpty()) { + QTimer::singleShot(5000, this, SLOT(testForUpdate())); + } + + VersionInfo currentVersion(m_MOVersion); + VersionInfo newestVersion(m_NewestVersion); + + if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) { + emit updateAvailable(); + } else if (newestVersion < currentVersion) { + qDebug("this version is newer than the current version on nexus (%s vs %s)", + currentVersion.canonicalString().toUtf8().constData(), + newestVersion.canonicalString().toUtf8().constData()); + } + } +} + + +void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID != m_UpdateRequestID) { + return; + } + QString version = userData.toString(); + + m_UpdateRequestID = -1; + + if (!resultData.canConvert<QVariantList>()) { + qCritical("invalid files result: %s", resultData.toString().toUtf8().constData()); + reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log.")); + return; + } + + QVariantList result = resultData.toList(); + + QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version)); + QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version)); + int updateFileID = -1; + QString updateFileName; + int mainFileID = -1; + QString mainFileName; + int mainFileSize = 0; + + foreach(QVariant file, result) { + QVariantMap fileInfo = file.toMap(); + if (!fileInfo["uri"].toString().endsWith(".7z")) { + continue; + } + + if (fileInfo["version"].toString() == version) { + if (fileInfo["category_id"].toInt() == 2) { + QString description = fileInfo["description"].toString(); + // update + if (updateExpList.indexIn(description) != -1) { + // there is an update for the newest version of MO, but does + // it apply to the current version? + QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts); + if (supportedVersions.contains(m_MOVersion.canonicalString())) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else if (updateExpRange.indexIn(description) != -1) { + VersionInfo rangeLowEnd(updateExpRange.cap(1)); + VersionInfo rangeHighEnd(updateExpRange.cap(2)); + if ((rangeLowEnd <= m_MOVersion) && + (m_MOVersion <= rangeHighEnd)) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + break; + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else { + qWarning("invalid update description: %s", + description.toUtf8().constData()); + } + } else if (fileInfo["category_id"].toInt() == 1) { + mainFileID = fileInfo["id"].toInt(); + mainFileName = fileInfo["uri"].toString(); + mainFileSize = fileInfo["size"].toInt(); + } + } + } + + if (updateFileID != -1) { + qDebug("update available: %d", updateFileID); + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + updateFileID, this, updateFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } else if (mainFileID != -1) { + qDebug("full download required: %d", mainFileID); + if (QMessageBox::question(m_Parent, tr("Update"), + tr("No incremental update available for this version, " + "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + mainFileID, this, mainFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } else { + qCritical("no file for update found"); + MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent); + m_Progress.hide(); + } +} + + +void SelfUpdater::nxmRequestFailed(int, QVariant, int requestID, const QString &errorMessage) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + if (m_Attempts > 0) { + QTimer::singleShot(60000, this, SLOT(testForUpdate())); + --m_Attempts; + } else { + MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent); + } + } +} + + +void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + QVariantList serverList = resultData.toList(); + if (serverList.count() != 0) { + qSort(serverList.begin(), serverList.end(), DownloadManager::ServerByPreference); + + QVariantMap dlServer = serverList.first().toMap(); + + download(dlServer["URI"].toString(), userData.toString()); + } else { + MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent); + m_Progress.hide(); + } + } +} + diff --git a/src/selfupdater.h b/src/selfupdater.h index 3a92b769..2c4c1ecd 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -17,129 +17,129 @@ 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 SELFUPDATER_H
-#define SELFUPDATER_H
-
-
-#include "nexusinterface.h"
-#include <archive.h>
-#include <versioninfo.h>
-
-#include <QObject>
-#include <QNetworkReply>
-#include <QFile>
-#include <QProgressDialog>
-
-
-/**
- * @brief manages updates for Mod Organizer itself
- * This class is used to update the Mod Organizer
- * The process looks like this:
- * 1. call testForUpdate() to determine is available
- * 2. if the updateAvailable() signal is received, allow the user to start the update
- * 3. if the user start the update, call startUpdate()
- * 4. startUpdate() will first query a list of files, try to determine if there is an
- * incremental update. If not, the user will have to confirm the download of a full download.
- * Once the correct file is selected, it is downloaded.
- * 5. before the downloaded file is extracted, existing files that are going to be replaced are
- * moved to "update_backup" on because files that are currently open can't be replaced.
- * 6. the update is extracted and then deleted
- * 7. finally, a restart is requested via signal.
- * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files
- * should now be open
- *
- * @todo use NexusBridge
- **/
-class SelfUpdater : public QObject
-{
-
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- * @param nexusInterface interface to query information from nexus
- * @param parent parent widget
- * @todo passing the nexus interface is unneccessary
- **/
- SelfUpdater(NexusInterface *nexusInterface, QWidget *parent);
- ~SelfUpdater();
-
- /**
- * @brief start the update process
- * @note this should not be called if there is no update available
- **/
- void startUpdate();
-
- /**
- * @return current version of Mod Organizer
- **/
- VersionInfo getVersion() const { return m_MOVersion; }
-
-public slots:
-
- /**
- * @brief request information about the current version
- **/
- void testForUpdate();
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
- void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorMessage);
- void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
-
-signals:
-
- /**
- * @brief emitted if a restart of the client is necessary to complete the update
- **/
- void restart();
-
- /**
- * @brief emitted if an update is available
- **/
- void updateAvailable();
-
- /**
- * @brief emitted if a message of the day was received
- **/
- void motdAvailable(const QString &motd);
-
-private:
-
- void download(const QString &downloadLink, const QString &fileName);
- void installUpdate();
- void queryPassword(LPSTR password);
- void updateProgress(float percentage);
- void updateProgressFile(LPCWSTR fileName);
- void report7ZipError(LPCWSTR errorMessage);
- QString retrieveNews(const QString &description);
-
-private slots:
-
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
- void downloadReadyRead();
- void downloadFinished();
- void downloadCancel();
-
-private:
-
- QWidget *m_Parent;
- VersionInfo m_MOVersion;
- NexusInterface *m_Interface;
- int m_UpdateRequestID;
- QString m_NewestVersion;
- QFile m_UpdateFile;
- QNetworkReply *m_Reply;
- QProgressDialog m_Progress;
- bool m_Canceled;
- int m_Attempts;
-
- Archive *m_CurrentArchive;
-
-};
-
-
-#endif // SELFUPDATER_H
+#ifndef SELFUPDATER_H +#define SELFUPDATER_H + + +#include "nexusinterface.h" +#include <archive.h> +#include <versioninfo.h> + +#include <QObject> +#include <QNetworkReply> +#include <QFile> +#include <QProgressDialog> + + +/** + * @brief manages updates for Mod Organizer itself + * This class is used to update the Mod Organizer + * The process looks like this: + * 1. call testForUpdate() to determine is available + * 2. if the updateAvailable() signal is received, allow the user to start the update + * 3. if the user start the update, call startUpdate() + * 4. startUpdate() will first query a list of files, try to determine if there is an + * incremental update. If not, the user will have to confirm the download of a full download. + * Once the correct file is selected, it is downloaded. + * 5. before the downloaded file is extracted, existing files that are going to be replaced are + * moved to "update_backup" on because files that are currently open can't be replaced. + * 6. the update is extracted and then deleted + * 7. finally, a restart is requested via signal. + * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files + * should now be open + * + * @todo use NexusBridge + **/ +class SelfUpdater : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param nexusInterface interface to query information from nexus + * @param parent parent widget + * @todo passing the nexus interface is unneccessary + **/ + SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); + ~SelfUpdater(); + + /** + * @brief start the update process + * @note this should not be called if there is no update available + **/ + void startUpdate(); + + /** + * @return current version of Mod Organizer + **/ + MOBase::VersionInfo getVersion() const { return m_MOVersion; } + +public slots: + + /** + * @brief request information about the current version + **/ + void testForUpdate(); + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorMessage); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + +signals: + + /** + * @brief emitted if a restart of the client is necessary to complete the update + **/ + void restart(); + + /** + * @brief emitted if an update is available + **/ + void updateAvailable(); + + /** + * @brief emitted if a message of the day was received + **/ + void motdAvailable(const QString &motd); + +private: + + void download(const QString &downloadLink, const QString &fileName); + void installUpdate(); + void queryPassword(LPSTR password); + void updateProgress(float percentage); + void updateProgressFile(LPCWSTR fileName); + void report7ZipError(LPCWSTR errorMessage); + QString retrieveNews(const QString &description); + +private slots: + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadReadyRead(); + void downloadFinished(); + void downloadCancel(); + +private: + + QWidget *m_Parent; + MOBase::VersionInfo m_MOVersion; + NexusInterface *m_Interface; + int m_UpdateRequestID; + QString m_NewestVersion; + QFile m_UpdateFile; + QNetworkReply *m_Reply; + QProgressDialog m_Progress; + bool m_Canceled; + int m_Attempts; + + Archive *m_CurrentArchive; + +}; + + +#endif // SELFUPDATER_H diff --git a/src/settings.cpp b/src/settings.cpp index 7c12e815..e7553676 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -17,494 +17,498 @@ 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 "settings.h"
-
-#include "settingsdialog.h"
-#include "utility.h"
-#include "helper.h"
-#include <gameinfo.h>
-#include <appconfig.h>
-#include <utility.h>
-
-#include <QCheckBox>
-#include <QLineEdit>
-#include <QDirIterator>
-#include <QRegExp>
-#include <QCoreApplication>
-#include <QMessageBox>
-
-
-static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01,
- 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 };
-
-Settings *Settings::s_Instance = NULL;
-
-
-Settings::Settings()
- : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat)
-{
- if (s_Instance != NULL) {
- throw std::runtime_error("second instance of \"Settings\" created");
- } else {
- s_Instance = this;
- }
-}
-
-
-Settings::~Settings()
-{
- s_Instance = NULL;
-}
-
-
-Settings &Settings::instance()
-{
- if (s_Instance == NULL) {
- throw std::runtime_error("no instance of \"Settings\"");
- }
- return *s_Instance;
-}
-
-void Settings::clearPlugins()
-{
- m_Plugins.clear();
- m_PluginSettings.clear();
-}
-
-void Settings::registerPlugin(IPlugin *plugin)
-{
- m_Plugins.push_back(plugin);
- foreach (const PluginSetting &setting, plugin->settings()) {
- QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue);
- if (!temp.convert(setting.defaultValue.type())) {
- qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default",
- qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name()));
- temp = setting.defaultValue;
- }
- m_PluginSettings[plugin->name()][setting.key] = temp;
- }
-}
-
-
-QString Settings::obfuscate(const QString &password) const
-{
- QByteArray temp = password.toUtf8();
-
- QByteArray buffer;
- for (int i = 0; i < temp.length(); ++i) {
- buffer.append(temp.at(i) ^ Key2[i % 20]);
- }
- return buffer.toBase64();
-}
-
-
-QString Settings::deObfuscate(const QString &password) const
-{
- QByteArray temp(QByteArray::fromBase64(password.toUtf8()));
-
- QByteArray buffer;
- for (int i = 0; i < temp.length(); ++i) {
- buffer.append(temp.at(i) ^ Key2[i % 20]);
- }
- return QString::fromUtf8(buffer.constData());
-}
-
-
-bool Settings::hideUncheckedPlugins() const
-{
- return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool();
-}
-
-bool Settings::forceEnableCoreFiles() const
-{
- return m_Settings.value("Settings/force_enable_core_files", true).toBool();
-}
-
-bool Settings::automaticLoginEnabled() const
-{
- return m_Settings.value("Settings/nexus_login", false).toBool();
-}
-
-QString Settings::getSteamAppID() const
-{
- return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId())).toString();
-}
-
-QString Settings::getDownloadDirectory() const
-{
- return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString());
-}
-
-QString Settings::getCacheDirectory() const
-{
- return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString());
-}
-
-QString Settings::getModDirectory() const
-{
- return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString());
-}
-
-QString Settings::getNMMVersion() const
-{
- return m_Settings.value("Settings/nmm_version", "0.34.0").toString();
-}
-
-bool Settings::getNexusLogin(QString &username, QString &password) const
-{
- if (m_Settings.value("Settings/nexus_login", false).toBool()) {
- username = m_Settings.value("Settings/nexus_username", "").toString();
- password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString());
- return true;
- } else {
- return false;
- }
-}
-
-int Settings::logLevel() const
-{
- return m_Settings.value("Settings/log_level", 0).toInt();
-}
-
-
-void Settings::setNexusLogin(QString username, QString password)
-{
- m_Settings.setValue("Settings/nexus_login", true);
- m_Settings.setValue("Settings/nexus_username", username);
- m_Settings.setValue("Settings/nexus_password", obfuscate(password));
-}
-
-
-LoadMechanism::EMechanism Settings::getLoadMechanism() const
-{
- switch (m_Settings.value("Settings/load_mechanism").toInt()) {
- case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER;
- case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER;
- case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL;
- }
- throw std::runtime_error("invalid load mechanism");
-}
-
-
-void Settings::setupLoadMechanism()
-{
- m_LoadMechanism.activate(getLoadMechanism());
-}
-
-
-bool Settings::preferIntegratedInstallers()
-{
- return m_Settings.value("Settings/prefer_integrated_installer").toBool();
-}
-
-
-bool Settings::enableQuickInstaller()
-{
- return m_Settings.value("Settings/enable_quick_installer").toBool();
-}
-
-
-bool Settings::preferExternalBrowser()
-{
- return m_Settings.value("Settings/prefer_external_browser").toBool();
-}
-
-
-void Settings::setMotDHash(uint hash)
-{
- m_Settings.setValue("motd_hash", hash);
-}
-
-uint Settings::getMotDHash() const
-{
- return m_Settings.value("motd_hash", 0).toUInt();
-}
-
-QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const
-{
- auto iterPlugin = m_PluginSettings.find(pluginName);
- if (iterPlugin == m_PluginSettings.end()) {
- throw MyException(tr("setting for invalid plugin \"%1\" requested").arg(key));
- }
- auto iterSetting = iterPlugin->find(key);
- if (iterSetting == iterPlugin->end()) {
- throw MyException(tr("invalid setting \"%1\" requested for plugin \"%2\"").arg(key).arg(pluginName));
- }
-
- return *iterSetting;
-}
-
-
-void Settings::addLanguages(QComboBox *languageBox)
-{
- languageBox->addItem("English", "en_US");
-
- QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files);
- QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm";
- QRegExp exp(pattern);
- while (langIter.hasNext()) {
- langIter.next();
- QString file = langIter.fileName();
- if (exp.exactMatch(file)) {
- QString languageCode = exp.cap(1);
- QLocale locale(languageCode);
- QString languageString = QLocale::languageToString(locale.language());
- if (locale.language() == QLocale::Chinese) {
- if (languageCode == "zh_TW") {
- languageString = "Chinese (traditional)";
- } else {
- languageString = "Chinese (simplified)";
- }
- }
-
- languageBox->addItem(QString("%1").arg(languageString), exp.cap(1));
- }
- }
-}
-
-void Settings::addStyles(QComboBox *styleBox)
-{
- styleBox->addItem("None", "");
- QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files);
- while (langIter.hasNext()) {
- langIter.next();
- QString style = langIter.fileName();
- styleBox->addItem(style, style);
- }
-}
-
-bool Settings::isNXMHandler(bool *modifyable)
-{
- QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\shell\\open\\command",
- QSettings::NativeFormat);
-
- QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData();
- QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\"");
- if (modifyable != NULL) {
- handlerReg.setValue("Default", currentExe);
- handlerReg.sync();
-
- *modifyable = handlerReg.status() == QSettings::NoError;
- // QSettings::isWritable returns wrong results...
- }
- return currentExe == myExe;
-}
-
-
-void Settings::setNXMHandlerActive(bool active, bool writable)
-{
- QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\",
- QSettings::NativeFormat);
-
- if (writable) {
- QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\"");
- handlerReg.setValue("Default", "URL:NXM Protocol");
- handlerReg.setValue("URL Protocol", "");
- handlerReg.setValue("shell/open/command/Default", active ? myExe : "");
- handlerReg.sync();
- } else {
- Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active);
- }
-}
-
-
-void Settings::resetDialogs()
-{
- m_Settings.beginGroup("DialogChoices");
- QStringList keys = m_Settings.childKeys();
- foreach (QString key, keys) {
- m_Settings.remove(key);
- }
-
- m_Settings.endGroup();
-}
-
-
-void Settings::query(QWidget *parent)
-{
- SettingsDialog dialog(parent);
-
- connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs()));
-
- QCheckBox *hideUncheckedBox = dialog.findChild<QCheckBox*>("hideUncheckedBox");
- QCheckBox *forceEnableBox = dialog.findChild<QCheckBox*>("forceEnableBox");
- QComboBox *mechanismBox = dialog.findChild<QComboBox*>("mechanismBox");
-
- QCheckBox *loginCheckBox = dialog.findChild<QCheckBox*>("loginCheckBox");
- QLineEdit *usernameEdit = dialog.findChild<QLineEdit*>("usernameEdit");
- QLineEdit *passwordEdit = dialog.findChild<QLineEdit*>("passwordEdit");
-
- QLineEdit *appIDEdit = dialog.findChild<QLineEdit*>("appIDEdit");
-
- QComboBox *languageBox = dialog.findChild<QComboBox*>("languageBox");
- QComboBox *styleBox = dialog.findChild<QComboBox*>("styleBox");
- QComboBox *logLevelBox = dialog.findChild<QComboBox*>("logLevelBox");
- QCheckBox *handleNXMBox = dialog.findChild<QCheckBox*>("handleNXMBox");
-
- QLineEdit *downloadDirEdit = dialog.findChild<QLineEdit*>("downloadDirEdit");
- QLineEdit *modDirEdit = dialog.findChild<QLineEdit*>("modDirEdit");
- QLineEdit *cacheDirEdit = dialog.findChild<QLineEdit*>("cacheDirEdit");
-
- QCheckBox *preferIntegratedCheckBox = dialog.findChild<QCheckBox*>("preferIntegratedBox");
- QCheckBox *preferExternalBox = dialog.findChild<QCheckBox*>("preferExternalBox");
- QCheckBox *quickInstallerBox = dialog.findChild<QCheckBox*>("quickInstallerBox");
- QLineEdit *nmmVersionEdit = dialog.findChild<QLineEdit*>("nmmVersionEdit");
-
- QListWidget *pluginsList = dialog.findChild<QListWidget*>("pluginsList");
-
- //
- // set up current settings
- //
- LoadMechanism::EMechanism mechanismID = getLoadMechanism();
- int index = 0;
-
- if (m_LoadMechanism.isDirectLoadingSupported()) {
- mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER);
- if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) {
- index = mechanismBox->count() - 1;
- }
- }
-
- if (m_LoadMechanism.isScriptExtenderSupported()) {
- mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER);
- if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) {
- index = mechanismBox->count() - 1;
- }
- }
-
- if (m_LoadMechanism.isProxyDLLSupported()) {
- mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL);
- if (mechanismID == LoadMechanism::LOAD_PROXYDLL) {
- index = mechanismBox->count() - 1;
- }
- }
-
- mechanismBox->setCurrentIndex(index);
-
- {
- addLanguages(languageBox);
- int currentID = languageBox->findData(m_Settings.value("Settings/language", QLocale::system().name()).toString());
- if (currentID != -1) {
- languageBox->setCurrentIndex(currentID);
- }
- }
-
- {
- addStyles(styleBox);
- int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString());
- if (currentID != -1) {
- styleBox->setCurrentIndex(currentID);
- }
- }
-
- hideUncheckedBox->setChecked(hideUncheckedPlugins());
- forceEnableBox->setChecked(forceEnableCoreFiles());
-
- appIDEdit->setText(getSteamAppID());
-
- if (automaticLoginEnabled()) {
- loginCheckBox->setChecked(true);
- usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString());
- passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()));
- }
-
- bool registryWritable = false;
- bool nxmHandler = isNXMHandler(®istryWritable);
- handleNXMBox->setChecked(nxmHandler);
- if (!registryWritable) {
- handleNXMBox->setIcon(QIcon(":/MO/gui/locked"));
- handleNXMBox->setToolTip(tr("Administrative rights required to change this."));
- }
-
- downloadDirEdit->setText(getDownloadDirectory());
- modDirEdit->setText(getModDirectory());
- cacheDirEdit->setText(getCacheDirectory());
- preferIntegratedCheckBox->setChecked(m_Settings.value("Settings/prefer_integrated_installer", false).toBool());
- preferExternalBox->setChecked(m_Settings.value("Settings/prefer_external_browser", false).toBool());
- quickInstallerBox->setChecked(m_Settings.value("Settings/enable_quick_installer", true).toBool());
- nmmVersionEdit->setText(m_Settings.value("Settings/nmm_version", "0.33.1").toString());
- logLevelBox->setCurrentIndex(logLevel());
-
- foreach (IPlugin *plugin, m_Plugins) {
- QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList);
- listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin));
- listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]);
- pluginsList->addItem(listItem);
- }
-
- if (dialog.exec() == QDialog::Accepted) {
- //
- // transfer modified settings to configuration file
- //
-
- m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false);
- m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false);
- m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt());
- if (QDir(downloadDirEdit->text()).exists()) {
- m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text()));
- }
- if (!QDir(cacheDirEdit->text()).exists()) {
- QDir().mkpath(cacheDirEdit->text());
- }
- m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text()));
- if (QDir(modDirEdit->text()).exists()) {
- if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) &&
- (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! "
- "Mods not present (or named differently) in the new location will be disabled in all profiles. "
- "There is no way to undo this unless you backed up your profiles manually. Proceed?"),
- QMessageBox::Yes | QMessageBox::No))) {
- m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text()));
- }
- }
- QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString();
- QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString();
- if (newLanguage != oldLanguage) {
- m_Settings.setValue("Settings/language", newLanguage);
- emit languageChanged(newLanguage);
- }
-
- QString oldStyle = m_Settings.value("Settings/style", "").toString();
- QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString();
- if (oldStyle != newStyle) {
- m_Settings.setValue("Settings/style", newStyle);
- emit styleChanged(newStyle);
- }
-
- m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex());
-
- if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) {
- m_Settings.setValue("Settings/app_id", appIDEdit->text());
- } else {
- m_Settings.remove("Settings/app_id");
- }
- if (loginCheckBox->isChecked()) {
- m_Settings.setValue("Settings/nexus_login", true);
- m_Settings.setValue("Settings/nexus_username", usernameEdit->text());
- m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text()));
- } else {
- m_Settings.setValue("Settings/nexus_login", false);
- m_Settings.remove("Settings/nexus_username");
- m_Settings.remove("Settings/nexus_password");
- }
- if (nxmHandler != handleNXMBox->isChecked()) {
- setNXMHandlerActive(handleNXMBox->isChecked(), registryWritable);
- }
- m_Settings.setValue("Settings/prefer_integrated_installer", preferIntegratedCheckBox->isChecked());
- m_Settings.setValue("Settings/prefer_external_browser", preferExternalBox->isChecked());
- m_Settings.setValue("Settings/enable_quick_installer", quickInstallerBox->isChecked());
-
- m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text());
-
- // transfer plugin settings to in-memory structure
- for (int i = 0; i < pluginsList->count(); ++i) {
- QListWidgetItem *item = pluginsList->item(i);
- m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap();
- }
- // store plugin settings on disc
- for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) {
- for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) {
- m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value());
- }
- }
- }
-}
+#include "settings.h" + +#include "settingsdialog.h" +#include "utility.h" +#include "helper.h" +#include <gameinfo.h> +#include <appconfig.h> +#include <utility.h> + +#include <QCheckBox> +#include <QLineEdit> +#include <QDirIterator> +#include <QRegExp> +#include <QCoreApplication> +#include <QMessageBox> + + +using namespace MOBase; +using namespace MOShared; + + +static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, + 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; + +Settings *Settings::s_Instance = NULL; + + +Settings::Settings() + : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) +{ + if (s_Instance != NULL) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } +} + + +Settings::~Settings() +{ + s_Instance = NULL; +} + + +Settings &Settings::instance() +{ + if (s_Instance == NULL) { + throw std::runtime_error("no instance of \"Settings\""); + } + return *s_Instance; +} + +void Settings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); +} + +void Settings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + foreach (const PluginSetting &setting, plugin->settings()) { + QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { + qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", + qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); + temp = setting.defaultValue; + } + m_PluginSettings[plugin->name()][setting.key] = temp; + } +} + + +QString Settings::obfuscate(const QString &password) const +{ + QByteArray temp = password.toUtf8(); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return buffer.toBase64(); +} + + +QString Settings::deObfuscate(const QString &password) const +{ + QByteArray temp(QByteArray::fromBase64(password.toUtf8())); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return QString::fromUtf8(buffer.constData()); +} + + +bool Settings::hideUncheckedPlugins() const +{ + return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); +} + +bool Settings::forceEnableCoreFiles() const +{ + return m_Settings.value("Settings/force_enable_core_files", true).toBool(); +} + +bool Settings::automaticLoginEnabled() const +{ + return m_Settings.value("Settings/nexus_login", false).toBool(); +} + +QString Settings::getSteamAppID() const +{ + return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId())).toString(); +} + +QString Settings::getDownloadDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); +} + +QString Settings::getCacheDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); +} + +QString Settings::getModDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); +} + +QString Settings::getNMMVersion() const +{ + return m_Settings.value("Settings/nmm_version", "0.34.0").toString(); +} + +bool Settings::getNexusLogin(QString &username, QString &password) const +{ + if (m_Settings.value("Settings/nexus_login", false).toBool()) { + username = m_Settings.value("Settings/nexus_username", "").toString(); + password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); + return true; + } else { + return false; + } +} + +int Settings::logLevel() const +{ + return m_Settings.value("Settings/log_level", 0).toInt(); +} + + +void Settings::setNexusLogin(QString username, QString password) +{ + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", username); + m_Settings.setValue("Settings/nexus_password", obfuscate(password)); +} + + +LoadMechanism::EMechanism Settings::getLoadMechanism() const +{ + switch (m_Settings.value("Settings/load_mechanism").toInt()) { + case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; + case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; + case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; + } + throw std::runtime_error("invalid load mechanism"); +} + + +void Settings::setupLoadMechanism() +{ + m_LoadMechanism.activate(getLoadMechanism()); +} + + +bool Settings::preferIntegratedInstallers() +{ + return m_Settings.value("Settings/prefer_integrated_installer").toBool(); +} + + +bool Settings::enableQuickInstaller() +{ + return m_Settings.value("Settings/enable_quick_installer").toBool(); +} + + +bool Settings::preferExternalBrowser() +{ + return m_Settings.value("Settings/prefer_external_browser").toBool(); +} + + +void Settings::setMotDHash(uint hash) +{ + m_Settings.setValue("motd_hash", hash); +} + +uint Settings::getMotDHash() const +{ + return m_Settings.value("motd_hash", 0).toUInt(); +} + +QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(tr("setting for invalid plugin \"%1\" requested").arg(key)); + } + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + throw MyException(tr("invalid setting \"%1\" requested for plugin \"%2\"").arg(key).arg(pluginName)); + } + + return *iterSetting; +} + + +void Settings::addLanguages(QComboBox *languageBox) +{ + languageBox->addItem("English", "en_US"); + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); + QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + QRegExp exp(pattern); + while (langIter.hasNext()) { + langIter.next(); + QString file = langIter.fileName(); + if (exp.exactMatch(file)) { + QString languageCode = exp.cap(1); + QLocale locale(languageCode); + QString languageString = QLocale::languageToString(locale.language()); + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (traditional)"; + } else { + languageString = "Chinese (simplified)"; + } + } + + languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); + } + } +} + +void Settings::addStyles(QComboBox *styleBox) +{ + styleBox->addItem("None", ""); + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); + while (langIter.hasNext()) { + langIter.next(); + QString style = langIter.fileName(); + styleBox->addItem(style, style); + } +} + +bool Settings::isNXMHandler(bool *modifyable) +{ + QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\shell\\open\\command", + QSettings::NativeFormat); + + QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); + QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); + if (modifyable != NULL) { + handlerReg.setValue("Default", currentExe); + handlerReg.sync(); + + *modifyable = handlerReg.status() == QSettings::NoError; + // QSettings::isWritable returns wrong results... + } + return currentExe == myExe; +} + + +void Settings::setNXMHandlerActive(bool active, bool writable) +{ + QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", + QSettings::NativeFormat); + + if (writable) { + QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); + handlerReg.setValue("Default", "URL:NXM Protocol"); + handlerReg.setValue("URL Protocol", ""); + handlerReg.setValue("shell/open/command/Default", active ? myExe : ""); + handlerReg.sync(); + } else { + Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active); + } +} + + +void Settings::resetDialogs() +{ + m_Settings.beginGroup("DialogChoices"); + QStringList keys = m_Settings.childKeys(); + foreach (QString key, keys) { + m_Settings.remove(key); + } + + m_Settings.endGroup(); +} + + +void Settings::query(QWidget *parent) +{ + SettingsDialog dialog(parent); + + connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); + + QCheckBox *hideUncheckedBox = dialog.findChild<QCheckBox*>("hideUncheckedBox"); + QCheckBox *forceEnableBox = dialog.findChild<QCheckBox*>("forceEnableBox"); + QComboBox *mechanismBox = dialog.findChild<QComboBox*>("mechanismBox"); + + QCheckBox *loginCheckBox = dialog.findChild<QCheckBox*>("loginCheckBox"); + QLineEdit *usernameEdit = dialog.findChild<QLineEdit*>("usernameEdit"); + QLineEdit *passwordEdit = dialog.findChild<QLineEdit*>("passwordEdit"); + + QLineEdit *appIDEdit = dialog.findChild<QLineEdit*>("appIDEdit"); + + QComboBox *languageBox = dialog.findChild<QComboBox*>("languageBox"); + QComboBox *styleBox = dialog.findChild<QComboBox*>("styleBox"); + QComboBox *logLevelBox = dialog.findChild<QComboBox*>("logLevelBox"); + QCheckBox *handleNXMBox = dialog.findChild<QCheckBox*>("handleNXMBox"); + + QLineEdit *downloadDirEdit = dialog.findChild<QLineEdit*>("downloadDirEdit"); + QLineEdit *modDirEdit = dialog.findChild<QLineEdit*>("modDirEdit"); + QLineEdit *cacheDirEdit = dialog.findChild<QLineEdit*>("cacheDirEdit"); + + QCheckBox *preferIntegratedCheckBox = dialog.findChild<QCheckBox*>("preferIntegratedBox"); + QCheckBox *preferExternalBox = dialog.findChild<QCheckBox*>("preferExternalBox"); + QCheckBox *quickInstallerBox = dialog.findChild<QCheckBox*>("quickInstallerBox"); + QLineEdit *nmmVersionEdit = dialog.findChild<QLineEdit*>("nmmVersionEdit"); + + QListWidget *pluginsList = dialog.findChild<QListWidget*>("pluginsList"); + + // + // set up current settings + // + LoadMechanism::EMechanism mechanismID = getLoadMechanism(); + int index = 0; + + if (m_LoadMechanism.isDirectLoadingSupported()) { + mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); + if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isScriptExtenderSupported()) { + mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); + if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isProxyDLLSupported()) { + mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); + if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { + index = mechanismBox->count() - 1; + } + } + + mechanismBox->setCurrentIndex(index); + + { + addLanguages(languageBox); + int currentID = languageBox->findData(m_Settings.value("Settings/language", QLocale::system().name()).toString()); + if (currentID != -1) { + languageBox->setCurrentIndex(currentID); + } + } + + { + addStyles(styleBox); + int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + styleBox->setCurrentIndex(currentID); + } + } + + hideUncheckedBox->setChecked(hideUncheckedPlugins()); + forceEnableBox->setChecked(forceEnableCoreFiles()); + + appIDEdit->setText(getSteamAppID()); + + if (automaticLoginEnabled()) { + loginCheckBox->setChecked(true); + usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); + passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); + } + + bool registryWritable = false; + bool nxmHandler = isNXMHandler(®istryWritable); + handleNXMBox->setChecked(nxmHandler); + if (!registryWritable) { + handleNXMBox->setIcon(QIcon(":/MO/gui/locked")); + handleNXMBox->setToolTip(tr("Administrative rights required to change this.")); + } + + downloadDirEdit->setText(getDownloadDirectory()); + modDirEdit->setText(getModDirectory()); + cacheDirEdit->setText(getCacheDirectory()); + preferIntegratedCheckBox->setChecked(m_Settings.value("Settings/prefer_integrated_installer", false).toBool()); + preferExternalBox->setChecked(m_Settings.value("Settings/prefer_external_browser", false).toBool()); + quickInstallerBox->setChecked(m_Settings.value("Settings/enable_quick_installer", true).toBool()); + nmmVersionEdit->setText(m_Settings.value("Settings/nmm_version", "0.33.1").toString()); + logLevelBox->setCurrentIndex(logLevel()); + + foreach (IPlugin *plugin, m_Plugins) { + QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); + listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); + listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); + pluginsList->addItem(listItem); + } + + if (dialog.exec() == QDialog::Accepted) { + // + // transfer modified settings to configuration file + // + + m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); + m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); + m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); + if (QDir(downloadDirEdit->text()).exists()) { + m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); + } + if (!QDir(cacheDirEdit->text()).exists()) { + QDir().mkpath(cacheDirEdit->text()); + } + m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); + if (QDir(modDirEdit->text()).exists()) { + if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && + (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " + "Mods not present (or named differently) in the new location will be disabled in all profiles. " + "There is no way to undo this unless you backed up your profiles manually. Proceed?"), + QMessageBox::Yes | QMessageBox::No))) { + m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); + } + } + QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); + QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { + m_Settings.setValue("Settings/language", newLanguage); + emit languageChanged(newLanguage); + } + + QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { + m_Settings.setValue("Settings/style", newStyle); + emit styleChanged(newStyle); + } + + m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); + + if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { + m_Settings.setValue("Settings/app_id", appIDEdit->text()); + } else { + m_Settings.remove("Settings/app_id"); + } + if (loginCheckBox->isChecked()) { + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); + m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text())); + } else { + m_Settings.setValue("Settings/nexus_login", false); + m_Settings.remove("Settings/nexus_username"); + m_Settings.remove("Settings/nexus_password"); + } + if (nxmHandler != handleNXMBox->isChecked()) { + setNXMHandlerActive(handleNXMBox->isChecked(), registryWritable); + } + m_Settings.setValue("Settings/prefer_integrated_installer", preferIntegratedCheckBox->isChecked()); + m_Settings.setValue("Settings/prefer_external_browser", preferExternalBox->isChecked()); + m_Settings.setValue("Settings/enable_quick_installer", quickInstallerBox->isChecked()); + + m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); + + // transfer plugin settings to in-memory structure + for (int i = 0; i < pluginsList->count(); ++i) { + QListWidgetItem *item = pluginsList->item(i); + m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + } + // store plugin settings on disc + for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { + m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + } + } + } +} diff --git a/src/settings.h b/src/settings.h index d02de1a6..04781611 100644 --- a/src/settings.h +++ b/src/settings.h @@ -17,207 +17,207 @@ 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 WORKAROUNDS_H
-#define WORKAROUNDS_H
-
-#include "loadmechanism.h"
-
-#include <iplugin.h>
-
-#include <QSettings>
-#include <QListWidget>
-#include <QComboBox>
-
-
-/**
- * manages the settings for Mod Organizer. The settings are not cached
- * inside the class but read/written directly from/to disc
- **/
-class Settings : public QObject
-{
-
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- **/
- Settings();
-
- virtual ~Settings();
-
- static Settings &instance();
-
- /**
- * unregister all plugins from settings
- */
- void clearPlugins();
-
- /**
- * @brief register plugin to be configurable
- * @param plugin the plugin to register
- */
- void registerPlugin(IPlugin *plugin);
-
- /**
- * displays a SettingsDialog that allows the user to change settings. If the
- * user accepts the changes, the settings are immediately written
- **/
- void query(QWidget *parent);
-
- /**
- * set up the settings for the specified plugins
- **/
- void addPluginSettings(const std::vector<IPlugin*> &plugins);
-
- /**
- * @return true if the user wants unchecked plugins (esp, esm) should be hidden from
- * the virtual dat adirectory
- **/
- bool hideUncheckedPlugins() const;
-
- /**
- * @return true if files of the core game are forced-enabled so the user can't accidentally disable them
- */
- bool forceEnableCoreFiles() const;
-
- /**
- * the steam appid is assigned by the steam platform to each product sold there.
- * The appid may differ between different versions of a game so it may be impossible
- * for Mod Organizer to automatically recognize it, though usually it does
- * @return the steam appid for the game
- **/
- QString getSteamAppID() const;
-
- /**
- * retrieve the directory where downloads are stored (with native separators)
- **/
- QString getDownloadDirectory() const;
-
- /**
- * retrieve the directory where mods are stored (with native separators)
- **/
- QString getModDirectory() const;
-
- /**
- * returns the version of nmm to impersonate when connecting to nexus
- **/
- QString getNMMVersion() const;
-
- /**
- * retrieve the directory where the web cache is stored (with native separators)
- **/
- QString getCacheDirectory() const;
-
- /**
- * @return true if the user has set up automatic login to nexus
- **/
- bool automaticLoginEnabled() const;
-
- /**
- * @brief retrieve the login information for nexus
- *
- * @param username (out) receives the user name for nexus
- * @param password (out) received the password for nexus
- * @return true if automatic login is active, false otherwise
- **/
- bool getNexusLogin(QString &username, QString &password) const;
-
- /**
- * @return the configured log level
- */
- int logLevel() const;
-
- /**
- * @brief set the nexus login information
- *
- * @param username username
- * @param password password
- */
- void setNexusLogin(QString username, QString password);
-
- /**
- * @return the load mechanism to be used
- **/
- LoadMechanism::EMechanism getLoadMechanism() const;
-
- /**
- * @brief activate the load mechanism selected by the user
- **/
- void setupLoadMechanism();
-
- /**
- * @return true if the user prefers the integrated installer over external variants
- **/
- bool preferIntegratedInstallers();
-
- /**
- * @return true if the user prefers to use an external browser over the integrated one
- **/
- bool preferExternalBrowser();
-
- /**
- * @return true if the user has enabled the quick installer (default true)
- **/
- bool enableQuickInstaller();
-
- /**
- * @brief sets the new motd hash
- **/
- void setMotDHash(uint hash);
-
- /**
- * @return hash of the last displayed message of the day
- **/
- uint getMotDHash() const;
-
- /**
- * @brief allows direct access to the wrapped QSettings object
- * @return the wrapped QSettings object
- */
- QSettings &directInterface() { return m_Settings; }
-
- /**
- * @brief retrieve a setting for one of the installed plugins
- * @param pluginName name of the plugin
- * @param key name of the setting to retrieve
- * @return the requested value as a QVariant
- * @throws an exception is thrown if this setting doesn't exist
- */
- QVariant pluginSetting(const QString &pluginName, const QString &key) const;
-
-private:
-
- QString obfuscate(const QString &password) const;
- QString deObfuscate(const QString &password) const;
-
- void addLanguages(QComboBox *languageBox);
- void addStyles(QComboBox *styleBox);
- bool isNXMHandler(bool *modifyable);
- void setNXMHandlerActive(bool active, bool writable);
-
-private slots:
-
- void resetDialogs();
-
-signals:
-
- void languageChanged(const QString &newLanguage);
- void styleChanged(const QString &newStyle);
-
-private:
-
- static Settings *s_Instance;
-
- QSettings m_Settings;
-
- LoadMechanism m_LoadMechanism;
-
- std::vector<IPlugin*> m_Plugins;
-
- QMap<QString, QMap<QString, QVariant> > m_PluginSettings;
-
-};
-
-#endif // WORKAROUNDS_H
+#ifndef WORKAROUNDS_H +#define WORKAROUNDS_H + +#include "loadmechanism.h" + +#include <iplugin.h> + +#include <QSettings> +#include <QListWidget> +#include <QComboBox> + + +/** + * manages the settings for Mod Organizer. The settings are not cached + * inside the class but read/written directly from/to disc + **/ +class Settings : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + **/ + Settings(); + + virtual ~Settings(); + + static Settings &instance(); + + /** + * unregister all plugins from settings + */ + void clearPlugins(); + + /** + * @brief register plugin to be configurable + * @param plugin the plugin to register + */ + void registerPlugin(MOBase::IPlugin *plugin); + + /** + * displays a SettingsDialog that allows the user to change settings. If the + * user accepts the changes, the settings are immediately written + **/ + void query(QWidget *parent); + + /** + * set up the settings for the specified plugins + **/ + void addPluginSettings(const std::vector<MOBase::IPlugin*> &plugins); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual dat adirectory + **/ + bool hideUncheckedPlugins() const; + + /** + * @return true if files of the core game are forced-enabled so the user can't accidentally disable them + */ + bool forceEnableCoreFiles() const; + + /** + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString getSteamAppID() const; + + /** + * retrieve the directory where downloads are stored (with native separators) + **/ + QString getDownloadDirectory() const; + + /** + * retrieve the directory where mods are stored (with native separators) + **/ + QString getModDirectory() const; + + /** + * returns the version of nmm to impersonate when connecting to nexus + **/ + QString getNMMVersion() const; + + /** + * retrieve the directory where the web cache is stored (with native separators) + **/ + QString getCacheDirectory() const; + + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; + + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool getNexusLogin(QString &username, QString &password) const; + + /** + * @return the configured log level + */ + int logLevel() const; + + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + void setNexusLogin(QString username, QString password); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism getLoadMechanism() const; + + /** + * @brief activate the load mechanism selected by the user + **/ + void setupLoadMechanism(); + + /** + * @return true if the user prefers the integrated installer over external variants + **/ + bool preferIntegratedInstallers(); + + /** + * @return true if the user prefers to use an external browser over the integrated one + **/ + bool preferExternalBrowser(); + + /** + * @return true if the user has enabled the quick installer (default true) + **/ + bool enableQuickInstaller(); + + /** + * @brief sets the new motd hash + **/ + void setMotDHash(uint hash); + + /** + * @return hash of the last displayed message of the day + **/ + uint getMotDHash() const; + + /** + * @brief allows direct access to the wrapped QSettings object + * @return the wrapped QSettings object + */ + QSettings &directInterface() { return m_Settings; } + + /** + * @brief retrieve a setting for one of the installed plugins + * @param pluginName name of the plugin + * @param key name of the setting to retrieve + * @return the requested value as a QVariant + * @throws an exception is thrown if this setting doesn't exist + */ + QVariant pluginSetting(const QString &pluginName, const QString &key) const; + +private: + + QString obfuscate(const QString &password) const; + QString deObfuscate(const QString &password) const; + + void addLanguages(QComboBox *languageBox); + void addStyles(QComboBox *styleBox); + bool isNXMHandler(bool *modifyable); + void setNXMHandlerActive(bool active, bool writable); + +private slots: + + void resetDialogs(); + +signals: + + void languageChanged(const QString &newLanguage); + void styleChanged(const QString &newStyle); + +private: + + static Settings *s_Instance; + + QSettings m_Settings; + + LoadMechanism m_LoadMechanism; + + std::vector<MOBase::IPlugin*> m_Plugins; + + QMap<QString, QMap<QString, QVariant> > m_PluginSettings; + +}; + +#endif // WORKAROUNDS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 855acdf1..8fb990c1 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -17,133 +17,137 @@ 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 "settingsdialog.h"
-#include "ui_settingsdialog.h"
-#include "categoriesdialog.h"
-#include "helper.h"
-#include <gameinfo.h>
-#include <QDirIterator>
-#include <QFileDialog>
-#include <QMessageBox>
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
-
-SettingsDialog::SettingsDialog(QWidget *parent)
- : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog)
-{
- ui->setupUi(this);
-}
-
-SettingsDialog::~SettingsDialog()
-{
- delete ui;
-}
-
-void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins)
-{
- foreach (IPlugin *plugin, plugins) {
- ui->pluginsList->addItem(plugin->name());
- }
-}
-
-void SettingsDialog::accept()
-{
- storeSettings(ui->pluginsList->currentItem());
- TutorableDialog::accept();
-}
-
-
-void SettingsDialog::on_loginCheckBox_toggled(bool checked)
-{
- QLineEdit *usernameEdit = findChild<QLineEdit*>("usernameEdit");
- QLineEdit *passwordEdit = findChild<QLineEdit*>("passwordEdit");
- if (checked) {
- passwordEdit->setEnabled(true);
- usernameEdit->setEnabled(true);
- } else {
- passwordEdit->setEnabled(false);
- usernameEdit->setEnabled(false);
- }
-}
-
-void SettingsDialog::on_categoriesBtn_clicked()
-{
- CategoriesDialog dialog(this);
- if (dialog.exec() == QDialog::Accepted) {
- dialog.commitChanges();
- }
-}
-
-void SettingsDialog::on_bsaDateBtn_clicked()
-{
- Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data"));
-}
-
-void SettingsDialog::on_browseDownloadDirBtn_clicked()
-{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text());
- if (!temp.isEmpty()) {
- ui->downloadDirEdit->setText(temp);
- }
-}
-
-void SettingsDialog::on_browseModDirBtn_clicked()
-{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text());
- if (!temp.isEmpty()) {
- ui->modDirEdit->setText(temp);
- }
-}
-
-void SettingsDialog::on_browseCacheDirBtn_clicked()
-{
- QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text());
- if (!temp.isEmpty()) {
- ui->cacheDirEdit->setText(temp);
- }
-}
-
-void SettingsDialog::on_resetDialogsButton_clicked()
-{
- if (QMessageBox::question(this, tr("Confirm?"),
- tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- emit resetDialogs();
- }
-}
-
-void SettingsDialog::storeSettings(QListWidgetItem *pluginItem)
-{
- if (pluginItem != NULL) {
- QMap<QString, QVariant> settings = pluginItem->data(Qt::UserRole + 1).toMap();
-
- for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) {
- const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i);
- settings[item->text(0)] = item->data(1, Qt::DisplayRole);
- }
-
- pluginItem->setData(Qt::UserRole + 1, settings);
- }
-}
-
-void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
-{
- storeSettings(previous);
-
- ui->pluginSettingsList->clear();
- IPlugin *plugin = static_cast<IPlugin*>(current->data(Qt::UserRole).value<void*>());
- ui->authorLabel->setText(plugin->author());
- ui->versionLabel->setText(plugin->version().canonicalString());
- ui->descriptionLabel->setText(plugin->description());
-
- QMap<QString, QVariant> settings = current->data(Qt::UserRole + 1).toMap();
- ui->pluginSettingsList->setEnabled(settings.count() != 0);
- for (auto iter = settings.begin(); iter != settings.end(); ++iter) {
- QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key()));
- newItem->setData(1, Qt::DisplayRole, *iter);
- newItem->setData(1, Qt::EditRole, *iter);
- newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
- ui->pluginSettingsList->addTopLevelItem(newItem);
- }
-}
+#include "settingsdialog.h" +#include "ui_settingsdialog.h" +#include "categoriesdialog.h" +#include "helper.h" +#include <gameinfo.h> +#include <QDirIterator> +#include <QFileDialog> +#include <QMessageBox> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + + +using namespace MOBase; +using namespace MOShared; + + +SettingsDialog::SettingsDialog(QWidget *parent) + : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins) +{ + foreach (IPlugin *plugin, plugins) { + ui->pluginsList->addItem(plugin->name()); + } +} + +void SettingsDialog::accept() +{ + storeSettings(ui->pluginsList->currentItem()); + TutorableDialog::accept(); +} + + +void SettingsDialog::on_loginCheckBox_toggled(bool checked) +{ + QLineEdit *usernameEdit = findChild<QLineEdit*>("usernameEdit"); + QLineEdit *passwordEdit = findChild<QLineEdit*>("passwordEdit"); + if (checked) { + passwordEdit->setEnabled(true); + usernameEdit->setEnabled(true); + } else { + passwordEdit->setEnabled(false); + usernameEdit->setEnabled(false); + } +} + +void SettingsDialog::on_categoriesBtn_clicked() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void SettingsDialog::on_bsaDateBtn_clicked() +{ + Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); +} + +void SettingsDialog::on_browseDownloadDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->downloadDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseModDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->modDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseCacheDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); + if (!temp.isEmpty()) { + ui->cacheDirEdit->setText(temp); + } +} + +void SettingsDialog::on_resetDialogsButton_clicked() +{ + if (QMessageBox::question(this, tr("Confirm?"), + tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit resetDialogs(); + } +} + +void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) +{ + if (pluginItem != NULL) { + QMap<QString, QVariant> settings = pluginItem->data(Qt::UserRole + 1).toMap(); + + for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { + const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); + settings[item->text(0)] = item->data(1, Qt::DisplayRole); + } + + pluginItem->setData(Qt::UserRole + 1, settings); + } +} + +void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + storeSettings(previous); + + ui->pluginSettingsList->clear(); + IPlugin *plugin = static_cast<IPlugin*>(current->data(Qt::UserRole).value<void*>()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QMap<QString, QVariant> settings = current->data(Qt::UserRole + 1).toMap(); + ui->pluginSettingsList->setEnabled(settings.count() != 0); + for (auto iter = settings.begin(); iter != settings.end(); ++iter) { + QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); + newItem->setData(1, Qt::DisplayRole, *iter); + newItem->setData(1, Qt::EditRole, *iter); + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui->pluginSettingsList->addTopLevelItem(newItem); + } +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 5cb3470a..3bfc1706 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -17,64 +17,64 @@ 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 WORKAROUNDDIALOG_H
-#define WORKAROUNDDIALOG_H
-
-#include "tutorabledialog.h"
-#include <iplugin.h>
-#include <QDialog>
-#include <QListWidgetItem>
-
-namespace Ui {
- class SettingsDialog;
-}
-
-/**
- * dialog used to change settings for Mod Organizer. On top of the
- * settings managed by the "Settings" class, this offers a button to open the
- * CategoriesDialog
- **/
-class SettingsDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- explicit SettingsDialog(QWidget *parent = 0);
- ~SettingsDialog();
-
- void addPlugins(const std::vector<IPlugin*> &plugins);
-
-public slots:
-
- virtual void accept();
-
-signals:
-
- void resetDialogs();
-
-private:
-
- void storeSettings(QListWidgetItem *pluginItem);
-
-private slots:
- void on_loginCheckBox_toggled(bool checked);
-
- void on_categoriesBtn_clicked();
-
- void on_bsaDateBtn_clicked();
-
- void on_browseDownloadDirBtn_clicked();
-
- void on_browseModDirBtn_clicked();
-
- void on_browseCacheDirBtn_clicked();
-
- void on_resetDialogsButton_clicked();
-
- void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
-
-private:
- Ui::SettingsDialog *ui;
-};
-
-#endif // WORKAROUNDDIALOG_H
+#ifndef WORKAROUNDDIALOG_H +#define WORKAROUNDDIALOG_H + +#include "tutorabledialog.h" +#include <iplugin.h> +#include <QDialog> +#include <QListWidgetItem> + +namespace Ui { + class SettingsDialog; +} + +/** + * dialog used to change settings for Mod Organizer. On top of the + * settings managed by the "Settings" class, this offers a button to open the + * CategoriesDialog + **/ +class SettingsDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = 0); + ~SettingsDialog(); + + void addPlugins(const std::vector<MOBase::IPlugin*> &plugins); + +public slots: + + virtual void accept(); + +signals: + + void resetDialogs(); + +private: + + void storeSettings(QListWidgetItem *pluginItem); + +private slots: + void on_loginCheckBox_toggled(bool checked); + + void on_categoriesBtn_clicked(); + + void on_bsaDateBtn_clicked(); + + void on_browseDownloadDirBtn_clicked(); + + void on_browseModDirBtn_clicked(); + + void on_browseCacheDirBtn_clicked(); + + void on_resetDialogsButton_clicked(); + + void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + +private: + Ui::SettingsDialog *ui; +}; + +#endif // WORKAROUNDDIALOG_H diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 0d933d0a..3f96bff1 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "appconfig.h"
namespace AppConfig {
@@ -24,7 +24,10 @@ namespace AppConfig { #define PARWSTRING wstring
#define APPPARAM(partype, parid, value) partype parid ## () { return value; }
#include "appconfig.inc"
+
+namespace MOShared {
#undef PARWSTRING
#undef APPPARAM
}
+} // namespace MOShared diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h index b10ecc3c..54544255 100644 --- a/src/shared/appconfig.h +++ b/src/shared/appconfig.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 APPCONFIG_H
#define APPCONFIG_H
@@ -28,9 +28,13 @@ namespace AppConfig { #define PARWSTRING wstring
#define APPPARAM(partype, parid, value) partype parid ## ();
#include "appconfig.inc"
+
+namespace MOShared {
#undef PARWSTRING
#undef APPPARAM
}
+} // namespace MOShared
+
#endif // APPCONFIG_H
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index aa3bce5f..eaba91a6 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -1,799 +1,802 @@ -/* -Copyright (C) 2012 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 "directoryentry.h" -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include <sstream> -#include <algorithm> -#include <bsatk.h> -#include "error_report.h" -#include "util.h" -#include "windows_error.h" -#include <boost/bind.hpp> - - - -class OriginConnection { - -public: - - typedef int Index; - static const int INVALID_INDEX = INT_MIN; - -public: - - OriginConnection() - : m_NextID(0) - {} - - FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection) { - int newID = createID(); - m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection); - m_OriginsNameMap[originName] = newID; - m_OriginsPriorityMap[priority] = newID; - return m_Origins[newID]; - } - - bool exists(const std::wstring &name) { - std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name); - return iter != m_OriginsNameMap.end(); - } - - FilesOrigin &getByID(Index ID) { - return m_Origins[ID]; - } - - FilesOrigin &getByName(const std::wstring &name) { - std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name); - if (iter != m_OriginsNameMap.end()) { - return m_Origins[iter->second]; - } else { - std::ostringstream stream; - stream << "invalid origin name: " << ToString(name, false); - throw std::runtime_error(stream.str()); - } - } - - void changePriorityLookup(int oldPriority, int newPriority) - { - auto iter = m_OriginsPriorityMap.find(oldPriority); - if (iter != m_OriginsPriorityMap.end()) { - Index idx = iter->second; - m_OriginsPriorityMap.erase(iter); - m_OriginsPriorityMap[newPriority] = idx; - } - } - - void changeNameLookup(const std::wstring &oldName, const std::wstring &newName) - { - auto iter = m_OriginsNameMap.find(oldName); - if (iter != m_OriginsNameMap.end()) { - Index idx = iter->second; - m_OriginsNameMap.erase(iter); - m_OriginsNameMap[newName] = idx; - } else { - log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); - } - } - -private: - - Index createID() { - return m_NextID++; - } - -private: - - Index m_NextID; - - std::map<Index, FilesOrigin> m_Origins; - std::map<std::wstring, Index> m_OriginsNameMap; - std::map<int, Index> m_OriginsPriorityMap; - -}; - - -// -// FilesOrigin -// - - -void FilesOrigin::enable(bool enabled) -{ - if (!enabled) { - std::set<FileEntry::Index> copy = m_Files; - for (auto iter = copy.begin(); iter != copy.end(); ++iter) { - m_FileRegister->removeOrigin(*iter, m_ID); - } - m_Files.clear(); - } - m_Disabled = !enabled; -} - - -void FilesOrigin::removeFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - m_Files.erase(iter); - } -} - - - -std::wstring tail(const std::wstring &source, const size_t count) -{ - if (count >= source.length()) { - return source; - } - - return source.substr(source.length() - count); -} - - -void FilesOrigin::setPriority(int priority) -{ - m_OriginConnection->changePriorityLookup(m_Priority, priority); - - m_Priority = priority; -} - - -void FilesOrigin::setName(const std::wstring &name) -{ - m_OriginConnection->changeNameLookup(m_Name, name); - // change path too - if (tail(m_Path, m_Name.length()) == m_Name) { - m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); - } - m_Name = name; -} - -std::vector<FileEntry*> FilesOrigin::getFiles() const -{ - std::vector<FileEntry*> result; - - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - result.push_back(m_FileRegister->getFile(*iter)); - } - - return result; -} - - - -// -// FileEntry -// - -void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive) -{ - if (m_Origin == -1) { - m_Origin = origin; - m_FileTime = fileTime; - m_Archive = archive; -// } else if (FilesOrigin::getByID(origin).getPriority() > FilesOrigin::getByID(m_Origin).getPriority()) { - } else if (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) { - if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) { - m_Alternatives.push_back(m_Origin); - } - m_Origin = origin; - m_FileTime = fileTime; - m_Archive = archive; - } else { - bool found = false; - if (m_Origin == origin) { - // already an origin - return; - } - for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { - if (*iter == origin) { - // already an origin - return; - } - if (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority()) { - m_Alternatives.insert(iter, origin); - found = true; - break; - } - } - if (!found) { - m_Alternatives.push_back(origin); - } - } -} - -bool FileEntry::removeOrigin(int origin) -{ - if (m_Origin == origin) { - if (!m_Alternatives.empty()) { - // find alternative with the highest priority - std::vector<int>::iterator currentIter = m_Alternatives.begin(); - for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { - if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) && - (*iter != origin)) { - currentIter = iter; - } - } - int currentID = *currentIter; - m_Alternatives.erase(currentIter); - - m_Origin = currentID; - - // now we need to update the file time... - std::wstring filePath = getFullPath(); - HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (!::GetFileTime(file, NULL, NULL, &m_FileTime)) { - // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh - // the view to find out - m_Archive = L"bsa?"; - } else { - m_Archive = L""; - } - - ::CloseHandle(file); - - } else { - m_Origin = -1; - return true; - } - } else { - std::vector<int>::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin); - m_Alternatives.erase(newEnd, m_Alternatives.end()); - } - return false; -} - - -// sorted by priority descending -static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS) -{ - return entry->getOriginByID(LHS).getPriority() < entry->getOriginByID(RHS).getPriority(); -} - - -FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) - : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"") -{ -} - - -void FileEntry::sortOrigins() -{ - m_Alternatives.push_back(m_Origin); - std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2)); - m_Origin = m_Alternatives[m_Alternatives.size() - 1]; - m_Alternatives.pop_back(); -} - - -bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const -{ - if (parent == NULL) { - return false; - } else { - // don't append the topmost parent because it is the virtual data-root - if (recurseParents(path, parent->getParent())) { - path.append(L"\\").append(parent->getName()); - } - return true; - } -} - -std::wstring FileEntry::getFullPath() const -{ - std::wstring result; - bool ignore = false; - result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin - recurseParents(result, m_Parent); // all intermediate directories - result.append(L"\\").append(m_Name); // the actual filename - return result; -} - -std::wstring FileEntry::getRelativePath() const -{ - std::wstring result; - recurseParents(result, m_Parent); // all intermediate directories - result.append(L"\\").append(m_Name); // the actual filename - return result; -} - - - - - -// -// DirectoryEntry -// -DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID) - : m_OriginConnection(new OriginConnection), - m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID) -{ - m_FileRegister.reset(new FileRegister(m_OriginConnection)); -} - -DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, - boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection) - : m_FileRegister(fileRegister), m_OriginConnection(originConnection), - m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID) -{} - - -DirectoryEntry::~DirectoryEntry() -{ - clear(); -} - - -const std::wstring &DirectoryEntry::getName() const -{ - return m_Name; -} - - -void DirectoryEntry::clear() -{ - m_Files.clear(); - for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - delete *iter; - } - m_SubDirectories.clear(); -} - - -FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority) -{ - if (m_OriginConnection->exists(originName)) { - FilesOrigin &origin = m_OriginConnection->getByName(originName); - origin.enable(true); - return origin; - } else { - return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection); - } -} - - -void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority) -{ - FilesOrigin &origin = createOrigin(originName, directory, priority); - wchar_t *buffer = new wchar_t[MAXPATH_UNICODE + 1]; - memset(buffer, L'\0', MAXPATH_UNICODE + 1); - try { - int offset = _snwprintf(buffer, MAXPATH_UNICODE, L"%ls", directory.c_str()); - addFiles(origin, buffer, offset); - } catch (...) { - delete [] buffer; - buffer = NULL; - } - delete [] buffer; - m_Populated = true; -} - - -void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority) -{ - FilesOrigin &origin = createOrigin(originName, directory, priority); - - WIN32_FILE_ATTRIBUTE_DATA fileData; - if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { - throw windows_error("failed to determine file time"); - } - - BSA::Archive archive; - BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str()); - if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { - std::ostringstream stream; - stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); - throw std::runtime_error(stream.str()); - } - size_t namePos = fileName.find_last_of(L"\\/"); - if (namePos == std::wstring::npos) { - namePos = 0; - } else { - ++namePos; - } - - addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos)); - m_Populated = true; -} - - -void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) -{ - WIN32_FIND_DATAW findData; - - _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, L"\\*"); - HANDLE searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE); - if (searchHandle != INVALID_HANDLE_VALUE) { - BOOL result = true; - while (result) { - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if ((wcscmp(findData.cFileName, L".") != 0) && - (wcscmp(findData.cFileName, L"..") != 0)) { - int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); - // recurse into subdirectories - getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); - } - } else { - insert(findData.cFileName, origin, findData.ftLastWriteTime, L""); - } - result = ::FindNextFileW(searchHandle, &findData); - } - } - ::FindClose(searchHandle); -} - - -void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName) -{ - // add files - for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { - BSA::File::Ptr file = archiveFolder->getFile(fileIdx); - insert(ToWString(file->getName(), true), origin, fileTime, archiveName); - } - - // recurse into subdirectories - for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { - BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); - DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); - - folderEntry->addFiles(origin, folder, fileTime, archiveName); - } -} - - -void DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) -{ - size_t pos = filePath.find_first_of(L"\\/"); - if (pos == std::string::npos) { - this->remove(filePath, origin); - } else { - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); - if (entry != NULL) { - entry->removeFile(rest, origin); - } - } -} - -void DirectoryEntry::removeDirRecursive() -{ - while (!m_Files.empty()) { - m_FileRegister->removeFile(m_Files.begin()->second); - } - - for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - (*iter)->removeDirRecursive(); - } - m_SubDirectories.clear(); -} - -void DirectoryEntry::removeDir(const std::wstring &path) -{ - size_t pos = path.find_first_of(L"\\/"); - if (pos == std::string::npos) { - for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - if (_wcsicmp((*iter)->getName().c_str(), path.c_str()) == 0) { - (*iter)->removeDirRecursive(); - m_SubDirectories.erase(iter); - break; - } - } - } else { - std::wstring dirName = path.substr(0, pos); - std::wstring rest = path.substr(pos + 1); - DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); - if (entry != NULL) { - entry->removeDir(rest); - } - } -} - - -void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime) -{ - size_t pos = filePath.find_first_of(L"\\/"); - if (pos == std::string::npos) { - this->insert(filePath, origin, fileTime, L""); - } else { - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime); - } -} - - -void DirectoryEntry::removeFile(FileEntry::Index index) -{ - if (m_Files.size() != 0) { - auto iter = std::find_if(m_Files.begin(), m_Files.end(), - [&index](const std::pair<std::wstring, FileEntry::Index> &iter) -> bool { - return iter.second == index; } ); - if (iter != m_Files.end()) { - m_Files.erase(iter); - } else { - log("file \"%ls\" not in directory \"%ls\"", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); - } - } else { - log("file \"%ls\" not in directory \"%ls\", directory empty", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); - } -} - - -int DirectoryEntry::anyOrigin() const -{ - bool ignore; - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntry *entry = m_FileRegister->getFile(iter->second); - if (!entry->isFromArchive()) { - return entry->getOrigin(ignore); - } - } - - // if we got here, no file directly within this directory is a valid indicator for a mod, thus - // we continue looking in subdirectories - for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - int res = (*iter)->anyOrigin(); - if (res != -1){ - return res; - } - } - return m_Origin; -} - - -bool DirectoryEntry::originExists(const std::wstring &name) const -{ - return m_OriginConnection->exists(name); -} - - -FilesOrigin &DirectoryEntry::getOriginByID(int ID) const -{ - return m_OriginConnection->getByID(ID); -} - - -FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const -{ - return m_OriginConnection->getByName(name); -} - - -int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive) -{ - const DirectoryEntry *directory = NULL; - const FileEntry *file = searchFile(path, &directory); - if (file != NULL) { - return file->getOrigin(archive); - } else { - if (directory != NULL) { - return directory->anyOrigin(); - } else { - return -1; - } - } -} - -std::vector<FileEntry*> DirectoryEntry::getFiles() const -{ - std::vector<FileEntry*> result; - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - result.push_back(m_FileRegister->getFile(iter->second)); - } - return result; -} - - -const FileEntry *DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const -{ - if (directory != NULL) { - *directory = NULL; - } - - if ((path.length() == 0) || - (path == L"*")) { - // no file name -> the path ended on a (back-)slash - *directory = this; - - return NULL; - } - - size_t len = path.find_first_of(L"\\/"); - - if (len == std::string::npos) { - // no more path components - auto iter = m_Files.find(path); - if (iter != m_Files.end()) { - return m_FileRegister->getFile(iter->second); - } else if (directory != NULL) { - DirectoryEntry *temp = findSubDirectory(path); - if (temp != NULL) { - *directory = temp; - } - } - } else { - // file is in in a subdirectory, recurse into the matching subdirectory - std::wstring pathComponent = path.substr(0, len); - DirectoryEntry *temp = findSubDirectory(pathComponent); - if (temp != NULL) { - return temp->searchFile(path.substr(len + 1), directory); - } - } - return NULL; -} - -/* -void DirectoryEntry::sortOrigins() -{ - for (std::set<FileEntry>::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - const_cast<FileEntry&>(*iter).sortOrigins(); - } - for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - (*iter)->sortOrigins(); - } -} -*/ - -DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const -{ - for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) { - return *iter; - } - } - return NULL; -} - - - -const FileEntry *DirectoryEntry::findFile(const std::wstring &name) -{ - auto iter = m_Files.find(name); - if (iter != m_Files.end()) { - return m_FileRegister->getFile(iter->second); - } else { - return NULL; - } -} - -DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) -{ - for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) { - return *iter; - } - } - if (create) { - std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(), - new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection)); - return *iter; - } else { - return NULL; - } -} - - -DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID) -{ - if (path.length() == 0) { - // path ended with a backslash? - return this; - } - - size_t pos = path.find_first_of(L"\\/"); - if (pos == std::wstring::npos) { - return getSubDirectory(path, create); - } else { - DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID); - if (nextChild == NULL) { - return NULL; - } else { - return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID); - } - } -} - - - -FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection) - : m_OriginConnection(originConnection) -{ -} - -FileEntry::Index FileRegister::generateIndex() -{ - static FileEntry::Index sIndex = 0; - return sIndex++; -} - -bool FileRegister::indexValid(FileEntry::Index index) const -{ - return m_Files.find(index) != m_Files.end(); -} - -FileEntry &FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) -{ - FileEntry::Index index = generateIndex(); - m_Files[index] = FileEntry(index, name, parent); - return m_Files[index]; -} - - -FileEntry *FileRegister::getFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - return &iter->second; - } - return NULL; -} - - -void FileRegister::unregisterFile(FileEntry &file) -{ - bool ignore; - // unregister from origin - int originID = file.getOrigin(ignore); - m_OriginConnection->getByID(originID).removeFile(file.getIndex()); - const std::vector<int> &alternatives = file.getAlternatives(); - for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - m_OriginConnection->getByID(*iter).removeFile(file.getIndex()); - } - - // unregister from directory - if (file.getParent() != NULL) { - file.getParent()->removeFile(file.getIndex()); - } -} - - -void FileRegister::removeFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - unregisterFile(iter->second); - m_Files.erase(index); - } -} - -void FileRegister::removeOrigin(FileEntry::Index index, int originID) -{ - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - if (iter->second.removeOrigin(originID)) { - unregisterFile(iter->second); - } - } -} - -void FileRegister::sortOrigins() -{ - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - iter->second.sortOrigins(); - } -} +/*
+Copyright (C) 2012 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 "directoryentry.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <sstream>
+#include <algorithm>
+#include <bsatk.h>
+#include "error_report.h"
+#include "util.h"
+#include "windows_error.h"
+#include <boost/bind.hpp>
+
+namespace MOShared {
+
+
+
+class OriginConnection {
+
+public:
+
+ typedef int Index;
+ static const int INVALID_INDEX = INT_MIN;
+
+public:
+
+ OriginConnection()
+ : m_NextID(0)
+ {}
+
+ FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection) {
+ int newID = createID();
+ m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection);
+ m_OriginsNameMap[originName] = newID;
+ m_OriginsPriorityMap[priority] = newID;
+ return m_Origins[newID];
+ }
+
+ bool exists(const std::wstring &name) {
+ std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
+ return iter != m_OriginsNameMap.end();
+ }
+
+ FilesOrigin &getByID(Index ID) {
+ return m_Origins[ID];
+ }
+
+ FilesOrigin &getByName(const std::wstring &name) {
+ std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
+ if (iter != m_OriginsNameMap.end()) {
+ return m_Origins[iter->second];
+ } else {
+ std::ostringstream stream;
+ stream << "invalid origin name: " << ToString(name, false);
+ throw std::runtime_error(stream.str());
+ }
+ }
+
+ void changePriorityLookup(int oldPriority, int newPriority)
+ {
+ auto iter = m_OriginsPriorityMap.find(oldPriority);
+ if (iter != m_OriginsPriorityMap.end()) {
+ Index idx = iter->second;
+ m_OriginsPriorityMap.erase(iter);
+ m_OriginsPriorityMap[newPriority] = idx;
+ }
+ }
+
+ void changeNameLookup(const std::wstring &oldName, const std::wstring &newName)
+ {
+ auto iter = m_OriginsNameMap.find(oldName);
+ if (iter != m_OriginsNameMap.end()) {
+ Index idx = iter->second;
+ m_OriginsNameMap.erase(iter);
+ m_OriginsNameMap[newName] = idx;
+ } else {
+ log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str());
+ }
+ }
+
+private:
+
+ Index createID() {
+ return m_NextID++;
+ }
+
+private:
+
+ Index m_NextID;
+
+ std::map<Index, FilesOrigin> m_Origins;
+ std::map<std::wstring, Index> m_OriginsNameMap;
+ std::map<int, Index> m_OriginsPriorityMap;
+
+};
+
+
+//
+// FilesOrigin
+//
+
+
+void FilesOrigin::enable(bool enabled)
+{
+ if (!enabled) {
+ std::set<FileEntry::Index> copy = m_Files;
+ for (auto iter = copy.begin(); iter != copy.end(); ++iter) {
+ m_FileRegister->removeOrigin(*iter, m_ID);
+ }
+ m_Files.clear();
+ }
+ m_Disabled = !enabled;
+}
+
+
+void FilesOrigin::removeFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ m_Files.erase(iter);
+ }
+}
+
+
+
+std::wstring tail(const std::wstring &source, const size_t count)
+{
+ if (count >= source.length()) {
+ return source;
+ }
+
+ return source.substr(source.length() - count);
+}
+
+
+void FilesOrigin::setPriority(int priority)
+{
+ m_OriginConnection->changePriorityLookup(m_Priority, priority);
+
+ m_Priority = priority;
+}
+
+
+void FilesOrigin::setName(const std::wstring &name)
+{
+ m_OriginConnection->changeNameLookup(m_Name, name);
+ // change path too
+ if (tail(m_Path, m_Name.length()) == m_Name) {
+ m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name);
+ }
+ m_Name = name;
+}
+
+std::vector<FileEntry*> FilesOrigin::getFiles() const
+{
+ std::vector<FileEntry*> result;
+
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ result.push_back(m_FileRegister->getFile(*iter));
+ }
+
+ return result;
+}
+
+
+
+//
+// FileEntry
+//
+
+void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive)
+{
+ if (m_Origin == -1) {
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = archive;
+// } else if (FilesOrigin::getByID(origin).getPriority() > FilesOrigin::getByID(m_Origin).getPriority()) {
+ } else if (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) {
+ if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) {
+ m_Alternatives.push_back(m_Origin);
+ }
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = archive;
+ } else {
+ bool found = false;
+ if (m_Origin == origin) {
+ // already an origin
+ return;
+ }
+ for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if (*iter == origin) {
+ // already an origin
+ return;
+ }
+ if (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority()) {
+ m_Alternatives.insert(iter, origin);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ m_Alternatives.push_back(origin);
+ }
+ }
+}
+
+bool FileEntry::removeOrigin(int origin)
+{
+ if (m_Origin == origin) {
+ if (!m_Alternatives.empty()) {
+ // find alternative with the highest priority
+ std::vector<int>::iterator currentIter = m_Alternatives.begin();
+ for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) &&
+ (*iter != origin)) {
+ currentIter = iter;
+ }
+ }
+ int currentID = *currentIter;
+ m_Alternatives.erase(currentIter);
+
+ m_Origin = currentID;
+
+ // now we need to update the file time...
+ std::wstring filePath = getFullPath();
+ HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (!::GetFileTime(file, NULL, NULL, &m_FileTime)) {
+ // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh
+ // the view to find out
+ m_Archive = L"bsa?";
+ } else {
+ m_Archive = L"";
+ }
+
+ ::CloseHandle(file);
+
+ } else {
+ m_Origin = -1;
+ return true;
+ }
+ } else {
+ std::vector<int>::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin);
+ m_Alternatives.erase(newEnd, m_Alternatives.end());
+ }
+ return false;
+}
+
+
+// sorted by priority descending
+static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS)
+{
+ return entry->getOriginByID(LHS).getPriority() < entry->getOriginByID(RHS).getPriority();
+}
+
+
+FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent)
+ : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"")
+{
+}
+
+
+void FileEntry::sortOrigins()
+{
+ m_Alternatives.push_back(m_Origin);
+ std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2));
+ m_Origin = m_Alternatives[m_Alternatives.size() - 1];
+ m_Alternatives.pop_back();
+}
+
+
+bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const
+{
+ if (parent == NULL) {
+ return false;
+ } else {
+ // don't append the topmost parent because it is the virtual data-root
+ if (recurseParents(path, parent->getParent())) {
+ path.append(L"\\").append(parent->getName());
+ }
+ return true;
+ }
+}
+
+std::wstring FileEntry::getFullPath() const
+{
+ std::wstring result;
+ bool ignore = false;
+ result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin
+ recurseParents(result, m_Parent); // all intermediate directories
+ result.append(L"\\").append(m_Name); // the actual filename
+ return result;
+}
+
+std::wstring FileEntry::getRelativePath() const
+{
+ std::wstring result;
+ recurseParents(result, m_Parent); // all intermediate directories
+ result.append(L"\\").append(m_Name); // the actual filename
+ return result;
+}
+
+
+
+
+
+//
+// DirectoryEntry
+//
+DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID)
+ : m_OriginConnection(new OriginConnection),
+ m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
+{
+ m_FileRegister.reset(new FileRegister(m_OriginConnection));
+}
+
+DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID,
+ boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection)
+ : m_FileRegister(fileRegister), m_OriginConnection(originConnection),
+ m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
+{}
+
+
+DirectoryEntry::~DirectoryEntry()
+{
+ clear();
+}
+
+
+const std::wstring &DirectoryEntry::getName() const
+{
+ return m_Name;
+}
+
+
+void DirectoryEntry::clear()
+{
+ m_Files.clear();
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ delete *iter;
+ }
+ m_SubDirectories.clear();
+}
+
+
+FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
+{
+ if (m_OriginConnection->exists(originName)) {
+ FilesOrigin &origin = m_OriginConnection->getByName(originName);
+ origin.enable(true);
+ return origin;
+ } else {
+ return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection);
+ }
+}
+
+
+void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
+{
+ FilesOrigin &origin = createOrigin(originName, directory, priority);
+ wchar_t *buffer = new wchar_t[MAXPATH_UNICODE + 1];
+ memset(buffer, L'\0', MAXPATH_UNICODE + 1);
+ try {
+ int offset = _snwprintf(buffer, MAXPATH_UNICODE, L"%ls", directory.c_str());
+ addFiles(origin, buffer, offset);
+ } catch (...) {
+ delete [] buffer;
+ buffer = NULL;
+ }
+ delete [] buffer;
+ m_Populated = true;
+}
+
+
+void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority)
+{
+ FilesOrigin &origin = createOrigin(originName, directory, priority);
+
+ WIN32_FILE_ATTRIBUTE_DATA fileData;
+ if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) {
+ throw windows_error("failed to determine file time");
+ }
+
+ BSA::Archive archive;
+ BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str());
+ if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
+ std::ostringstream stream;
+ stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError();
+ throw std::runtime_error(stream.str());
+ }
+ size_t namePos = fileName.find_last_of(L"\\/");
+ if (namePos == std::wstring::npos) {
+ namePos = 0;
+ } else {
+ ++namePos;
+ }
+
+ addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos));
+ m_Populated = true;
+}
+
+
+void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset)
+{
+ WIN32_FIND_DATAW findData;
+
+ _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, L"\\*");
+ HANDLE searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE);
+ if (searchHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = true;
+ while (result) {
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if ((wcscmp(findData.cFileName, L".") != 0) &&
+ (wcscmp(findData.cFileName, L"..") != 0)) {
+ int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName);
+ // recurse into subdirectories
+ getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset);
+ }
+ } else {
+ insert(findData.cFileName, origin, findData.ftLastWriteTime, L"");
+ }
+ result = ::FindNextFileW(searchHandle, &findData);
+ }
+ }
+ ::FindClose(searchHandle);
+}
+
+
+void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName)
+{
+ // add files
+ for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) {
+ BSA::File::Ptr file = archiveFolder->getFile(fileIdx);
+ insert(ToWString(file->getName(), true), origin, fileTime, archiveName);
+ }
+
+ // recurse into subdirectories
+ for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) {
+ BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx);
+ DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID());
+
+ folderEntry->addFiles(origin, folder, fileTime, archiveName);
+ }
+}
+
+
+void DirectoryEntry::removeFile(const std::wstring &filePath, int *origin)
+{
+ size_t pos = filePath.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ this->remove(filePath, origin);
+ } else {
+ std::wstring dirName = filePath.substr(0, pos);
+ std::wstring rest = filePath.substr(pos + 1);
+ DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+ if (entry != NULL) {
+ entry->removeFile(rest, origin);
+ }
+ }
+}
+
+void DirectoryEntry::removeDirRecursive()
+{
+ while (!m_Files.empty()) {
+ m_FileRegister->removeFile(m_Files.begin()->second);
+ }
+
+ for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ (*iter)->removeDirRecursive();
+ }
+ m_SubDirectories.clear();
+}
+
+void DirectoryEntry::removeDir(const std::wstring &path)
+{
+ size_t pos = path.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), path.c_str()) == 0) {
+ (*iter)->removeDirRecursive();
+ m_SubDirectories.erase(iter);
+ break;
+ }
+ }
+ } else {
+ std::wstring dirName = path.substr(0, pos);
+ std::wstring rest = path.substr(pos + 1);
+ DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+ if (entry != NULL) {
+ entry->removeDir(rest);
+ }
+ }
+}
+
+
+void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime)
+{
+ size_t pos = filePath.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ this->insert(filePath, origin, fileTime, L"");
+ } else {
+ std::wstring dirName = filePath.substr(0, pos);
+ std::wstring rest = filePath.substr(pos + 1);
+ getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime);
+ }
+}
+
+
+void DirectoryEntry::removeFile(FileEntry::Index index)
+{
+ if (m_Files.size() != 0) {
+ auto iter = std::find_if(m_Files.begin(), m_Files.end(),
+ [&index](const std::pair<std::wstring, FileEntry::Index> &iter) -> bool {
+ return iter.second == index; } );
+ if (iter != m_Files.end()) {
+ m_Files.erase(iter);
+ } else {
+ log("file \"%ls\" not in directory \"%ls\"",
+ m_FileRegister->getFile(index)->getName().c_str(),
+ this->getName().c_str());
+ }
+ } else {
+ log("file \"%ls\" not in directory \"%ls\", directory empty",
+ m_FileRegister->getFile(index)->getName().c_str(),
+ this->getName().c_str());
+ }
+}
+
+
+int DirectoryEntry::anyOrigin() const
+{
+ bool ignore;
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ FileEntry *entry = m_FileRegister->getFile(iter->second);
+ if (!entry->isFromArchive()) {
+ return entry->getOrigin(ignore);
+ }
+ }
+
+ // if we got here, no file directly within this directory is a valid indicator for a mod, thus
+ // we continue looking in subdirectories
+ for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ int res = (*iter)->anyOrigin();
+ if (res != -1){
+ return res;
+ }
+ }
+ return m_Origin;
+}
+
+
+bool DirectoryEntry::originExists(const std::wstring &name) const
+{
+ return m_OriginConnection->exists(name);
+}
+
+
+FilesOrigin &DirectoryEntry::getOriginByID(int ID) const
+{
+ return m_OriginConnection->getByID(ID);
+}
+
+
+FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const
+{
+ return m_OriginConnection->getByName(name);
+}
+
+
+int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
+{
+ const DirectoryEntry *directory = NULL;
+ const FileEntry *file = searchFile(path, &directory);
+ if (file != NULL) {
+ return file->getOrigin(archive);
+ } else {
+ if (directory != NULL) {
+ return directory->anyOrigin();
+ } else {
+ return -1;
+ }
+ }
+}
+
+std::vector<FileEntry*> DirectoryEntry::getFiles() const
+{
+ std::vector<FileEntry*> result;
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ result.push_back(m_FileRegister->getFile(iter->second));
+ }
+ return result;
+}
+
+
+const FileEntry *DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const
+{
+ if (directory != NULL) {
+ *directory = NULL;
+ }
+
+ if ((path.length() == 0) ||
+ (path == L"*")) {
+ // no file name -> the path ended on a (back-)slash
+ *directory = this;
+
+ return NULL;
+ }
+
+ size_t len = path.find_first_of(L"\\/");
+
+ if (len == std::string::npos) {
+ // no more path components
+ auto iter = m_Files.find(path);
+ if (iter != m_Files.end()) {
+ return m_FileRegister->getFile(iter->second);
+ } else if (directory != NULL) {
+ DirectoryEntry *temp = findSubDirectory(path);
+ if (temp != NULL) {
+ *directory = temp;
+ }
+ }
+ } else {
+ // file is in in a subdirectory, recurse into the matching subdirectory
+ std::wstring pathComponent = path.substr(0, len);
+ DirectoryEntry *temp = findSubDirectory(pathComponent);
+ if (temp != NULL) {
+ return temp->searchFile(path.substr(len + 1), directory);
+ }
+ }
+ return NULL;
+}
+
+/*
+void DirectoryEntry::sortOrigins()
+{
+ for (std::set<FileEntry>::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ const_cast<FileEntry&>(*iter).sortOrigins();
+ }
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ (*iter)->sortOrigins();
+ }
+}
+*/
+
+DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const
+{
+ for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
+ return *iter;
+ }
+ }
+ return NULL;
+}
+
+
+
+const FileEntry *DirectoryEntry::findFile(const std::wstring &name)
+{
+ auto iter = m_Files.find(name);
+ if (iter != m_Files.end()) {
+ return m_FileRegister->getFile(iter->second);
+ } else {
+ return NULL;
+ }
+}
+
+DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID)
+{
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
+ return *iter;
+ }
+ }
+ if (create) {
+ std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(),
+ new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection));
+ return *iter;
+ } else {
+ return NULL;
+ }
+}
+
+
+DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID)
+{
+ if (path.length() == 0) {
+ // path ended with a backslash?
+ return this;
+ }
+
+ size_t pos = path.find_first_of(L"\\/");
+ if (pos == std::wstring::npos) {
+ return getSubDirectory(path, create);
+ } else {
+ DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID);
+ if (nextChild == NULL) {
+ return NULL;
+ } else {
+ return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID);
+ }
+ }
+}
+
+
+
+FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection)
+ : m_OriginConnection(originConnection)
+{
+}
+
+FileEntry::Index FileRegister::generateIndex()
+{
+ static FileEntry::Index sIndex = 0;
+ return sIndex++;
+}
+
+bool FileRegister::indexValid(FileEntry::Index index) const
+{
+ return m_Files.find(index) != m_Files.end();
+}
+
+FileEntry &FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent)
+{
+ FileEntry::Index index = generateIndex();
+ m_Files[index] = FileEntry(index, name, parent);
+ return m_Files[index];
+}
+
+
+FileEntry *FileRegister::getFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ return &iter->second;
+ }
+ return NULL;
+}
+
+
+void FileRegister::unregisterFile(FileEntry &file)
+{
+ bool ignore;
+ // unregister from origin
+ int originID = file.getOrigin(ignore);
+ m_OriginConnection->getByID(originID).removeFile(file.getIndex());
+ const std::vector<int> &alternatives = file.getAlternatives();
+ for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
+ m_OriginConnection->getByID(*iter).removeFile(file.getIndex());
+ }
+
+ // unregister from directory
+ if (file.getParent() != NULL) {
+ file.getParent()->removeFile(file.getIndex());
+ }
+}
+
+
+void FileRegister::removeFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ unregisterFile(iter->second);
+ m_Files.erase(index);
+ }
+}
+
+void FileRegister::removeOrigin(FileEntry::Index index, int originID)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ if (iter->second.removeOrigin(originID)) {
+ unregisterFile(iter->second);
+ }
+ }
+}
+
+void FileRegister::sortOrigins()
+{
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ iter->second.sortOrigins();
+ }
+}
+} // namespace MOShared
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 72e96357..e292861d 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 DIRECTORYENTRY_H
#define DIRECTORYENTRY_H
@@ -31,6 +31,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <boost/shared_ptr.hpp>
#include "util.h"
+namespace MOShared {
+
class DirectoryEntry;
class OriginConnection;
@@ -310,4 +312,6 @@ private: };
+} // namespace MOShared
+
#endif // DIRECTORYENTRY_H
diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 49fdba85..c1b25229 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -1,26 +1,28 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "error_report.h"
#include <sstream>
#include <stdio.h>
+namespace MOShared {
+
void reportError(LPCSTR format, ...)
{
@@ -97,3 +99,4 @@ std::wstring getCurrentErrorStringW() return result;
}
}
+} // namespace MOShared diff --git a/src/shared/error_report.h b/src/shared/error_report.h index 6ee296df..c09ad75b 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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/>.
+*/
+
#pragma once
#include <tchar.h>
@@ -32,11 +32,13 @@ typedef string tstring; #endif
}
+extern void log(const char* format, ...);
+
+namespace MOShared {
+
void reportError(LPCSTR format, ...);
void reportError(LPCWSTR format, ...);
-extern void log(const char* format, ...);
-
std::string getCurrentErrorStringA();
std::wstring getCurrentErrorStringW();
@@ -45,3 +47,5 @@ std::wstring getCurrentErrorStringW(); #else
#define getCurrentErrorString getCurrentErrorStringA
#endif
+
+} // namespace MOShared
diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index f9699145..a37e1119 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "fallout3info.h"
#include "util.h"
#include <tchar.h>
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <boost/assign.hpp>
+namespace MOShared {
+
Fallout3Info::Fallout3Info(const std::wstring &omoDirectory, const std::wstring &gameDirectory)
: GameInfo(omoDirectory, gameDirectory)
{
@@ -178,7 +180,7 @@ void Fallout3Info::createProfile(const std::wstring &directory, bool useDefaults source << getGameDirectory() << L"\\fallout_default.ini";
} else {
source << getMyGamesDirectory() << L"\\Fallout3";
- if (::FileExists(source.str(), L"fallout.ini")) {
+ if (FileExists(source.str(), L"fallout.ini")) {
source << L"\\fallout.ini";
} else {
source.str(L"");
@@ -242,3 +244,4 @@ std::vector<ExecutableInfo> Fallout3Info::getExecutables() return result;
}
+} // namespace MOShared
diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index bc9ab82d..9ec32027 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -1,28 +1,30 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 FALLOUT3INFO_H
#define FALLOUT3INFO_H
#include "gameinfo.h"
+namespace MOShared {
+
class Fallout3Info : public GameInfo
{
@@ -91,4 +93,6 @@ private: };
+} // namespace MOShared
+
#endif // FALLOUT3INFO_H
diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 380940b7..cd5586f3 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "falloutnvinfo.h"
#include "util.h"
#include <tchar.h>
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <boost/assign.hpp>
+namespace MOShared {
+
FalloutNVInfo::FalloutNVInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory)
: GameInfo(omoDirectory, gameDirectory)
@@ -156,7 +158,7 @@ void FalloutNVInfo::createProfile(const std::wstring &directory, bool useDefault source << getGameDirectory() << L"\\fallout_default.ini";
} else {
source << getMyGamesDirectory() << L"\\FalloutNV";
- if (::FileExists(source.str(), L"fallout.ini")) {
+ if (FileExists(source.str(), L"fallout.ini")) {
source << L"\\fallout.ini";
} else {
source.str(L"");
@@ -244,3 +246,4 @@ std::vector<ExecutableInfo> FalloutNVInfo::getExecutables() return result;
}
+} // namespace MOShared
diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index aa02ac30..53784177 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -1,28 +1,30 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 FALLOUTNVINFO_H
#define FALLOUTNVINFO_H
#include "gameinfo.h"
+namespace MOShared {
+
class FalloutNVInfo : public GameInfo
{
@@ -92,4 +94,6 @@ private: static bool identifyGame(const std::wstring &searchPath);
};
+} // namespace MOShared
+
#endif // FALLOUTNVINFO_H
diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index 6054df8b..fd5072bf 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "gameinfo.h"
#include "windows_error.h"
@@ -31,6 +31,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <sstream>
#include <cassert>
+namespace MOShared {
+
GameInfo* GameInfo::s_Instance = NULL;
@@ -202,3 +204,4 @@ std::wstring GameInfo::getSpecialPath(LPCWSTR name) const return temp;
}
+} // namespace MOShared diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 8828a2fc..fd8ba998 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 GAMEINFO_H
#define GAMEINFO_H
@@ -27,6 +27,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #define WIN32_LEAN_AND_MEAN
#include <Windows.h>
+namespace MOShared {
+
enum CloseMOStyle {
DEFAULT_CLOSE,
DEFAULT_STAY,
@@ -184,4 +186,6 @@ private: };
+} // namespace MOShared
+
#endif // GAMEINFO_H
diff --git a/src/shared/inject.cpp b/src/shared/inject.cpp index 8cedd247..9fea7fc8 100644 --- a/src/shared/inject.cpp +++ b/src/shared/inject.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "inject.h"
/*#if defined UNICODE && !defined _UNICODE
#define _UNICODE 1
@@ -31,6 +31,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "windows_error.h"
#include "error_report.h"
+namespace MOShared {
+
struct TParameters {
char dllname[MAX_PATH];
@@ -140,3 +142,5 @@ void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dll throw windows_error("failed to overwrite thread context");
}
}
+
+} // namespace MOShared
diff --git a/src/shared/inject.h b/src/shared/inject.h index 69c56cc6..d5030363 100644 --- a/src/shared/inject.h +++ b/src/shared/inject.h @@ -17,11 +17,15 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#pragma once
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-#include <string>
-
-void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel);
+#pragma once + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <string> + +namespace MOShared { + +void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); + +} diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index c34b2202..1c714072 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "oblivioninfo.h"
#include <tchar.h>
#include <ShlObj.h>
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <boost/assign.hpp>
+namespace MOShared {
+
OblivionInfo::OblivionInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory)
: GameInfo(omoDirectory, gameDirectory)
@@ -137,7 +139,7 @@ void OblivionInfo::createProfile(const std::wstring &directory, bool useDefaults source << getGameDirectory() << L"\\oblivion_default.ini";
} else {
source << getMyGamesDirectory() << L"Oblivion";
- if (::FileExists(source.str(), L"oblivion.ini")) {
+ if (FileExists(source.str(), L"oblivion.ini")) {
source << L"\\oblivion.ini";
} else {
source.str(L"");
@@ -250,3 +252,4 @@ std::vector<ExecutableInfo> OblivionInfo::getExecutables() return result;
}
+} // namespace MOShared
diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index 0d6bba65..51fd46e4 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -1,27 +1,29 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 OBLIVIONINFO_H
#define OBLIVIONINFO_H
#include "gameinfo.h"
+namespace MOShared {
+
class OblivionInfo : public GameInfo
{
@@ -89,4 +91,6 @@ private: };
+} // namespace MOShared
+
#endif // OBLIVIONINFO_H
diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index f3043c28..6302cef3 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "skyriminfo.h"
#include "util.h"
@@ -29,6 +29,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <boost/assign.hpp>
+namespace MOShared {
+
SkyrimInfo::SkyrimInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory)
: GameInfo(omoDirectory, gameDirectory)
@@ -206,7 +208,7 @@ void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) source << getGameDirectory() << L"\\skyrim_default.ini";
} else {
source << getMyGamesDirectory() << L"\\Skyrim";
- if (::FileExists(source.str(), L"skyrim.ini")) {
+ if (FileExists(source.str(), L"skyrim.ini")) {
source << L"\\skyrim.ini";
} else {
source.str(L"");
@@ -276,3 +278,4 @@ std::vector<ExecutableInfo> SkyrimInfo::getExecutables() return result;
}
+} // namespace MOShared
diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index 25de4029..b12a5d58 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -1,28 +1,30 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 SKYRIMINFO_H
#define SKYRIMINFO_H
#include "gameinfo.h"
+namespace MOShared {
+
class SkyrimInfo : public GameInfo
{
@@ -97,4 +99,6 @@ private: };
+} // namespace MOShared
+
#endif // SKYRIMINFO_H
diff --git a/src/shared/util.cpp b/src/shared/util.cpp index ec52dd38..c256e959 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1,27 +1,29 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "util.h"
#include "windows_error.h"
#include <sstream>
#include <algorithm>
+namespace MOShared {
+
bool FileExists(const std::wstring &filename)
{
@@ -117,3 +119,4 @@ VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) }
}
+} // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 7f44b55d..4faf0a30 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 UTIL_H
#define UTIL_H
@@ -25,6 +25,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #define WIN32_LEAN_AND_MEAN
#include <Windows.h>
+namespace MOShared {
+
static const int MAXPATH_UNICODE = 32767;
@@ -43,4 +45,6 @@ std::wstring ToLower(const std::wstring &text); VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName);
+} // namespace MOShared
+
#endif // UTIL_H
diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp index ad91db44..647a5bfb 100644 --- a/src/shared/windows_error.cpp +++ b/src/shared/windows_error.cpp @@ -1,25 +1,27 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 "windows_error.h"
#include <sstream>
+namespace MOShared {
+
std::string windows_error::constructMessage(const std::string& input, int inErrorCode)
{
std::ostringstream finalMessage;
@@ -43,3 +45,5 @@ std::string windows_error::constructMessage(const std::string& input, int inErro ::SetLastError(errorCode); // restore error code because FormatMessage might have modified it
return finalMessage.str();
}
+
+} // namespace MOShared
diff --git a/src/shared/windows_error.h b/src/shared/windows_error.h index 879ee1d2..20b19027 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 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/>. -*/ - +/*
+Copyright (C) 2012 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 WINDOWS_ERROR_H
#define WINDOWS_ERROR_H
@@ -25,6 +25,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #define WIN32_LEAN_AND_MEAN
#include <windows.h>
+namespace MOShared {
+
class windows_error : public std::runtime_error {
public:
windows_error(const std::string& message, int errorcode = -1)
@@ -37,4 +39,6 @@ private: int m_ErrorCode;
};
+} // namespace MOShared
+
#endif // WINDOWS_ERROR_H
diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index 69de45e2..029e8366 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -17,91 +17,91 @@ 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 "singleinstance.h"
-#include "report.h"
-#include "utility.h"
-#include <QLocalSocket>
-
-static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5";
-static const int s_Timeout = 5000;
-
-
-SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) :
- QObject(parent), m_PrimaryInstance(false)
-{
- m_SharedMem.setKey(s_Key);
- if (!m_SharedMem.create(1)) {
- if (forcePrimary) {
- while (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
- Sleep(500);
- if (m_SharedMem.create(1)) {
- m_PrimaryInstance = true;
- break;
- }
- }
- }
-
- if (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
- m_SharedMem.attach();
- m_PrimaryInstance = false;
- }
- if ((m_SharedMem.error() != QSharedMemory::NoError) &&
- (m_SharedMem.error() != QSharedMemory::AlreadyExists)) {
- throw MyException(tr("SHM error: %1").arg(m_SharedMem.errorString()));
- }
- } else {
- m_PrimaryInstance = true;
- }
- if (m_PrimaryInstance) {
- connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
- m_Server.listen(s_Key);
- }
-}
-
-
-void SingleInstance::sendMessage(const QString &message)
-{
- if (m_PrimaryInstance) {
- // nobody there to receive the message
- return;
- }
- QLocalSocket socket(this);
-
- bool connected = false;
- for(int i = 0; i < 2 && !connected; ++i) {
- if (i > 0) {
- Sleep(250);
- }
-
- // other instance may be just starting up
- socket.connectToServer(s_Key, QIODevice::WriteOnly);
- connected = socket.waitForConnected(s_Timeout);
- }
-
- if (!connected) {
- reportError(tr("failed to connect to running instance: %1").arg(socket.errorString()));
- return;
- }
-
- socket.write(message.toUtf8());
- if (!socket.waitForBytesWritten(s_Timeout)) {
- reportError(tr("failed to connect to running instance: %1").arg(socket.errorString()));
- return;
- }
-
- socket.disconnectFromServer();
-}
-
-
-void SingleInstance::receiveMessage()
-{
- QLocalSocket *socket = m_Server.nextPendingConnection();
- if (!socket->waitForReadyRead(s_Timeout)) {
- reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString()));
- return;
- }
-
- QString message = QString::fromUtf8(socket->readAll().constData());
- emit messageSent(message);
- socket->disconnectFromServer();
-}
+#include "singleinstance.h" +#include "report.h" +#include "utility.h" +#include <QLocalSocket> + +static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; +static const int s_Timeout = 5000; + + +SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : + QObject(parent), m_PrimaryInstance(false) +{ + m_SharedMem.setKey(s_Key); + if (!m_SharedMem.create(1)) { + if (forcePrimary) { + while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + Sleep(500); + if (m_SharedMem.create(1)) { + m_PrimaryInstance = true; + break; + } + } + } + + if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + m_SharedMem.attach(); + m_PrimaryInstance = false; + } + if ((m_SharedMem.error() != QSharedMemory::NoError) && + (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { + throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); + } + } else { + m_PrimaryInstance = true; + } + if (m_PrimaryInstance) { + connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); + m_Server.listen(s_Key); + } +} + + +void SingleInstance::sendMessage(const QString &message) +{ + if (m_PrimaryInstance) { + // nobody there to receive the message + return; + } + QLocalSocket socket(this); + + bool connected = false; + for(int i = 0; i < 2 && !connected; ++i) { + if (i > 0) { + Sleep(250); + } + + // other instance may be just starting up + socket.connectToServer(s_Key, QIODevice::WriteOnly); + connected = socket.waitForConnected(s_Timeout); + } + + if (!connected) { + reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); + return; + } + + socket.write(message.toUtf8()); + if (!socket.waitForBytesWritten(s_Timeout)) { + reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); + return; + } + + socket.disconnectFromServer(); +} + + +void SingleInstance::receiveMessage() +{ + QLocalSocket *socket = m_Server.nextPendingConnection(); + if (!socket->waitForReadyRead(s_Timeout)) { + reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + return; + } + + QString message = QString::fromUtf8(socket->readAll().constData()); + emit messageSent(message); + socket->disconnectFromServer(); +} diff --git a/src/spawn.cpp b/src/spawn.cpp index 13e74f93..39bd2af9 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -17,93 +17,97 @@ 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 "spawn.h"
-#include "report.h"
-#include "utility.h"
-#include <gameinfo.h>
-#include <inject.h>
-#include <appconfig.h>
-#include <windows_error.h>
-#include <QApplication>
-
-
-bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, HANDLE& processHandle, HANDLE& threadHandle)
-{
- STARTUPINFO si;
- ::ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
- int length = wcslen(binary) + wcslen(arguments) + 4;
- wchar_t *commandLine = NULL;
- if (arguments[0] != L'\0') {
- commandLine = new wchar_t[length];
- _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments);
- } else {
- commandLine = new wchar_t[length];
- _snwprintf(commandLine, length, L"\"%ls\"", binary);
-
- }
-
- PROCESS_INFORMATION pi;
- BOOL success = ::CreateProcess(NULL,
- commandLine,
- NULL, NULL, // no special process or thread attributes
- FALSE, // don't inherit handle
- suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL
- NULL, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
-
- delete [] commandLine;
-
- if (!success) {
- throw windows_error("failed to start process");
- }
-
- processHandle = pi.hProcess;
- threadHandle = pi.hThread;
- return true;
-}
-
-
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString& profileName, int logLevel, const QDir ¤tDirectory, bool hooked)
-{
- HANDLE processHandle, threadHandle;
- std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
- std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
-
- try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, processHandle, threadHandle)) {
- reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- } catch (const windows_error &e) {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what()));
- return INVALID_HANDLE_VALUE;
- }
-
- if (hooked) {
- try {
- QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()));
- if (!dllInfo.exists()) {
- reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- injectDLL(processHandle, threadHandle,
- QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(),
- ToWString(profileName).c_str(), logLevel);
- } catch (const windows_error& e) {
- reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what()));
- ::TerminateProcess(processHandle, 1);
- return INVALID_HANDLE_VALUE;
- }
-#ifdef _DEBUG
- reportError("ready?");
-#endif // DEBUG
- if (::ResumeThread(threadHandle) == (DWORD)-1) {
- reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- }
- return processHandle;
-}
+#include "spawn.h" +#include "report.h" +#include "utility.h" +#include <gameinfo.h> +#include <inject.h> +#include <appconfig.h> +#include <windows_error.h> +#include <QApplication> + + +using namespace MOBase; +using namespace MOShared; + + +bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, HANDLE& processHandle, HANDLE& threadHandle) +{ + STARTUPINFO si; + ::ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + int length = wcslen(binary) + wcslen(arguments) + 4; + wchar_t *commandLine = NULL; + if (arguments[0] != L'\0') { + commandLine = new wchar_t[length]; + _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); + } else { + commandLine = new wchar_t[length]; + _snwprintf(commandLine, length, L"\"%ls\"", binary); + + } + + PROCESS_INFORMATION pi; + BOOL success = ::CreateProcess(NULL, + commandLine, + NULL, NULL, // no special process or thread attributes + FALSE, // don't inherit handle + suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL + NULL, // same environment as parent + currentDirectory, // current directory + &si, &pi // startup and process information + ); + + delete [] commandLine; + + if (!success) { + throw windows_error("failed to start process"); + } + + processHandle = pi.hProcess; + threadHandle = pi.hThread; + return true; +} + + +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString& profileName, int logLevel, const QDir ¤tDirectory, bool hooked) +{ + HANDLE processHandle, threadHandle; + std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); + std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); + + try { + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, processHandle, threadHandle)) { + reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + } catch (const windows_error &e) { + reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); + return INVALID_HANDLE_VALUE; + } + + if (hooked) { + try { + QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName())); + if (!dllInfo.exists()) { + reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName())); + return INVALID_HANDLE_VALUE; + } + injectDLL(processHandle, threadHandle, + QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(), + ToWString(profileName).c_str(), logLevel); + } catch (const windows_error& e) { + reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what())); + ::TerminateProcess(processHandle, 1); + return INVALID_HANDLE_VALUE; + } +#ifdef _DEBUG + reportError("ready?"); +#endif // DEBUG + if (::ResumeThread(threadHandle) == (DWORD)-1) { + reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + } + return processHandle; +} diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 0fac8a7d..aae0fd7c 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -17,130 +17,134 @@ 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 "syncoverwritedialog.h"
-#include "ui_syncoverwritedialog.h"
-#include <utility.h>
-#include <report.h>
-#include <gameinfo.h>
-#include <QDir>
-#include <QDirIterator>
-#include <QComboBox>
-#include <QStringList>
-
-
-SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent)
- : TutorableDialog("SyncOverwrite", parent),
- ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure)
-{
- ui->setupUi(this);
- refresh(path, directoryStructure);
-
- QHeaderView *headerView = ui->syncTree->header();
-#if QT_VERSION >= 0x050000
- headerView->setSectionResizeMode(0, QHeaderView::Stretch);
- headerView->setSectionResizeMode(1, QHeaderView::Interactive);
-#else
- headerView->setResizeMode(0, QHeaderView::Stretch);
- headerView->setResizeMode(1, QHeaderView::Interactive);
-#endif
-}
-
-
-SyncOverwriteDialog::~SyncOverwriteDialog()
-{
- delete ui;
-}
-
-
-void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree)
-{
- QDir overwrite(path);
- overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
- QDirIterator dirIter(overwrite);
- while (dirIter.hasNext()) {
- dirIter.next();
- QFileInfo fileInfo = dirIter.fileInfo();
-
- QString file = fileInfo.fileName();
- if (file == "meta.ini") {
- continue;
- }
-
- QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file));
-
- if (fileInfo.isDir()) {
- DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file));
- if (subDir != NULL) {
- readTree(fileInfo.absoluteFilePath(), subDir, newItem);
- } else {
- qCritical("no directory structure for %s?", file.toUtf8().constData());
- delete newItem;
- newItem = NULL;
- }
- } else {
- const FileEntry *entry = directoryStructure->findFile(ToWString(file));
- QComboBox* combo = new QComboBox(ui->syncTree);
- combo->addItem(tr("<don't sync>"), -1);
- if (entry != NULL) {
- const std::vector<int> &alternatives = entry->getAlternatives();
- for (std::vector<int>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
- combo->addItem(ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter);
- }
- combo->setCurrentIndex(combo->count() - 1);
- } else {
- combo->setCurrentIndex(0);
- }
- ui->syncTree->setItemWidget(newItem, 1, combo);
- }
- if (newItem != NULL) {
- subTree->addChild(newItem);
- }
- }
-}
-
-
-void SyncOverwriteDialog::refresh(const QString &path, DirectoryEntry *directoryStructure)
-{
- QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("<data>"));
- readTree(path, directoryStructure, rootItem);
- ui->syncTree->addTopLevelItem(rootItem);
- ui->syncTree->expandAll();
-}
-
-
-void SyncOverwriteDialog::applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory)
-{
- for (int i = 0; i < item->childCount(); ++i) {
- QTreeWidgetItem *child = item->child(i);
- QString filePath;
- if (path.length() != 0) {
- filePath = path.mid(0).append("/").append(child->text(0));
- } else {
- filePath = child->text(0);
- }
- if (child->childCount() != 0) {
- applyTo(child, filePath, modDirectory);
- } else {
- QComboBox *comboBox = qobject_cast<QComboBox*>(ui->syncTree->itemWidget(child, 1));
- if (comboBox != NULL) {
- int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt();
- if (originID != -1) {
- FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID);
- QString source = m_SourcePath.mid(0).append("/").append(filePath);
- QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath);
- if (!QFile::remove(destination)) {
- reportError(tr("failed to remove %1").arg(destination));
- } else if (!QFile::rename(source, destination)) {
- reportError(tr("failed to move %1 to %2").arg(source).arg(destination));
- }
- }
- }
- }
- }
-}
-
-
-void SyncOverwriteDialog::apply(const QString &modDirectory)
-{
- applyTo(ui->syncTree->topLevelItem(0), "", modDirectory);
-}
+#include "syncoverwritedialog.h" +#include "ui_syncoverwritedialog.h" +#include <utility.h> +#include <report.h> +#include <gameinfo.h> +#include <QDir> +#include <QDirIterator> +#include <QComboBox> +#include <QStringList> + + +using namespace MOBase; +using namespace MOShared; + + +SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent) + : TutorableDialog("SyncOverwrite", parent), + ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure) +{ + ui->setupUi(this); + refresh(path, directoryStructure); + + QHeaderView *headerView = ui->syncTree->header(); +#if QT_VERSION >= 0x050000 + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Interactive); +#else + headerView->setResizeMode(0, QHeaderView::Stretch); + headerView->setResizeMode(1, QHeaderView::Interactive); +#endif +} + + +SyncOverwriteDialog::~SyncOverwriteDialog() +{ + delete ui; +} + + +void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree) +{ + QDir overwrite(path); + overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + QDirIterator dirIter(overwrite); + while (dirIter.hasNext()) { + dirIter.next(); + QFileInfo fileInfo = dirIter.fileInfo(); + + QString file = fileInfo.fileName(); + if (file == "meta.ini") { + continue; + } + + QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file)); + + if (fileInfo.isDir()) { + DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file)); + if (subDir != NULL) { + readTree(fileInfo.absoluteFilePath(), subDir, newItem); + } else { + qCritical("no directory structure for %s?", file.toUtf8().constData()); + delete newItem; + newItem = NULL; + } + } else { + const FileEntry *entry = directoryStructure->findFile(ToWString(file)); + QComboBox* combo = new QComboBox(ui->syncTree); + combo->addItem(tr("<don't sync>"), -1); + if (entry != NULL) { + const std::vector<int> &alternatives = entry->getAlternatives(); + for (std::vector<int>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + combo->addItem(ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter); + } + combo->setCurrentIndex(combo->count() - 1); + } else { + combo->setCurrentIndex(0); + } + ui->syncTree->setItemWidget(newItem, 1, combo); + } + if (newItem != NULL) { + subTree->addChild(newItem); + } + } +} + + +void SyncOverwriteDialog::refresh(const QString &path, DirectoryEntry *directoryStructure) +{ + QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("<data>")); + readTree(path, directoryStructure, rootItem); + ui->syncTree->addTopLevelItem(rootItem); + ui->syncTree->expandAll(); +} + + +void SyncOverwriteDialog::applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory) +{ + for (int i = 0; i < item->childCount(); ++i) { + QTreeWidgetItem *child = item->child(i); + QString filePath; + if (path.length() != 0) { + filePath = path.mid(0).append("/").append(child->text(0)); + } else { + filePath = child->text(0); + } + if (child->childCount() != 0) { + applyTo(child, filePath, modDirectory); + } else { + QComboBox *comboBox = qobject_cast<QComboBox*>(ui->syncTree->itemWidget(child, 1)); + if (comboBox != NULL) { + int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); + if (originID != -1) { + FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); + QString source = m_SourcePath.mid(0).append("/").append(filePath); + QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath); + if (!QFile::remove(destination)) { + reportError(tr("failed to remove %1").arg(destination)); + } else if (!QFile::rename(source, destination)) { + reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); + } + } + } + } + } +} + + +void SyncOverwriteDialog::apply(const QString &modDirectory) +{ + applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); +} diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h index ca4abb8e..da7c8cf5 100644 --- a/src/syncoverwritedialog.h +++ b/src/syncoverwritedialog.h @@ -17,39 +17,39 @@ 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 SYNCOVERWRITEDIALOG_H
-#define SYNCOVERWRITEDIALOG_H
-
-
-#include <directoryentry.h>
-#include "tutorabledialog.h"
-#include <QTreeWidgetItem>
-
-
-namespace Ui {
-class SyncOverwriteDialog;
-}
-
-class SyncOverwriteDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- explicit SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent = 0);
- ~SyncOverwriteDialog();
-
- void apply(const QString &modDirectory);
-private:
- void refresh(const QString &path, DirectoryEntry *directoryStructure);
- void readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree);
- void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory);
-
-private:
-
- Ui::SyncOverwriteDialog *ui;
- QString m_SourcePath;
- DirectoryEntry *m_DirectoryStructure;
-
-};
-
-#endif // SYNCOVERWRITEDIALOG_H
+#ifndef SYNCOVERWRITEDIALOG_H +#define SYNCOVERWRITEDIALOG_H + + +#include <directoryentry.h> +#include "tutorabledialog.h" +#include <QTreeWidgetItem> + + +namespace Ui { +class SyncOverwriteDialog; +} + +class SyncOverwriteDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0); + ~SyncOverwriteDialog(); + + void apply(const QString &modDirectory); +private: + void refresh(const QString &path, MOShared::DirectoryEntry *directoryStructure); + void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree); + void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory); + +private: + + Ui::SyncOverwriteDialog *ui; + QString m_SourcePath; + MOShared::DirectoryEntry *m_DirectoryStructure; + +}; + +#endif // SYNCOVERWRITEDIALOG_H diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index c6ddfd78..e69e4ab2 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -17,315 +17,319 @@ 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 "transfersavesdialog.h"
-#include "ui_transfersavesdialog.h"
-#include "utility.h"
-#include <gameinfo.h>
-#include <QDir>
-#include <QMessageBox>
-#include <Shlwapi.h>
-#include <shlobj.h>
-
-
-TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent)
- : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile)
-{
- ui->setupUi(this);
- refreshGlobalSaves();
- refreshLocalSaves();
- refreshGlobalCharacters();
- refreshLocalCharacters();
-}
-
-TransferSavesDialog::~TransferSavesDialog()
-{
- delete ui;
-}
-
-
-void TransferSavesDialog::refreshGlobalSaves()
-{
- m_GlobalSaves.clear();
-
- QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())));
-
- QStringList filters;
- filters << ToQString(GameInfo::instance().getSaveGameExtension());
- savesDir.setNameFilters(filters);
-
- QStringList files = savesDir.entryList(QDir::Files, QDir::Time);
-
- foreach (const QString &filename, files) {
- SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename));
- save->setParent(this);
- m_GlobalSaves.push_back(save);
- }
-}
-
-
-void TransferSavesDialog::refreshLocalSaves()
-{
- m_LocalSaves.clear();
-
- QDir savesDir(m_Profile.getPath().append("/saves"));
-
- QStringList filters;
- filters << ToQString(GameInfo::instance().getSaveGameExtension());
- savesDir.setNameFilters(filters);
-
- QStringList files = savesDir.entryList(QDir::Files, QDir::Time);
-
- foreach (const QString &filename, files) {
- SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename));
- save->setParent(this);
- m_LocalSaves.push_back(save);
- }
-}
-
-
-void TransferSavesDialog::refreshGlobalCharacters()
-{
- std::set<QString> characters;
- for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
- iter != m_GlobalSaves.end(); ++iter) {
- characters.insert((*iter)->pcName());
- }
- ui->globalCharacterList->clear();
- for (std::set<QString>::const_iterator iter = characters.begin();
- iter != characters.end(); ++iter) {
- ui->globalCharacterList->addItem(*iter);
- }
- if (ui->globalCharacterList->count() > 0) {
- ui->globalCharacterList->setCurrentRow(0);
- ui->copyToLocalBtn->setEnabled(true);
- ui->moveToLocalBtn->setEnabled(true);
- } else {
- ui->copyToLocalBtn->setEnabled(false);
- ui->moveToLocalBtn->setEnabled(false);
- }
-}
-
-
-void TransferSavesDialog::refreshLocalCharacters()
-{
- std::set<QString> characters;
- for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
- iter != m_LocalSaves.end(); ++iter) {
- characters.insert((*iter)->pcName());
- }
- ui->localCharacterList->clear();
- for (std::set<QString>::const_iterator iter = characters.begin();
- iter != characters.end(); ++iter) {
- ui->localCharacterList->addItem(*iter);
- }
- if (ui->localCharacterList->count() > 0) {
- ui->localCharacterList->setCurrentRow(0);
- ui->copyToGlobalBtn->setEnabled(true);
- ui->moveToGlobalBtn->setEnabled(true);
- } else {
- ui->copyToGlobalBtn->setEnabled(false);
- ui->moveToGlobalBtn->setEnabled(false);
- }
-}
-
-
-bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile)
-{
- QMessageBox::StandardButton res = overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No;
- if (overwriteMode == OVERWRITE_ASK) {
- res = QMessageBox::question(this, tr("Overwrite"),
- tr("Overwrite the file \"%1\"").arg(destinationFile),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll);
- if (res == QMessageBox::YesToAll) {
- overwriteMode = OVERWRITE_YES;
- res = QMessageBox::Yes;
- } else if (res == QMessageBox::NoToAll) {
- overwriteMode = OVERWRITE_NO;
- res = QMessageBox::No;
- }
- }
- return res == QMessageBox::Yes;
-}
-
-QStringList TransferSavesDialog::getFilesToProcess(const SaveGame *save)
-{
- QStringList result = save->attachedFiles();
- result.append(save->fileName());
- return result;
-}
-
-void TransferSavesDialog::on_moveToLocalBtn_clicked()
-{
- QString selectedCharacter = ui->globalCharacterList->currentItem()->text();
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- QString destination = m_Profile.getPath().append("/saves");
- OverwriteMode overwriteMode = OVERWRITE_ASK;
-
- for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
- iter != m_GlobalSaves.end(); ++iter) {
- if ((*iter)->pcName() == selectedCharacter) {
- QStringList files = getFilesToProcess(*iter);
- foreach (const QString &file, files) {
- QFileInfo fileInfo(file);
- QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
- if (QFile::exists(destinationFile)) {
- if (testOverwrite(overwriteMode, destinationFile)) {
- QFile::remove(destinationFile);
- } else {
- continue;
- }
- }
- if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) {
- qCritical("failed to move %s to %s",
- fileInfo.absoluteFilePath().toUtf8().constData(),
- destinationFile.toUtf8().constData());
- }
- }
- }
- }
- }
- refreshGlobalSaves();
- refreshGlobalCharacters();
- refreshLocalSaves();
- refreshLocalCharacters();
-}
-
-void TransferSavesDialog::on_copyToLocalBtn_clicked()
-{
- QString selectedCharacter = ui->globalCharacterList->currentItem()->text();
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- QString destination = m_Profile.getPath().append("/saves");
- OverwriteMode overwriteMode = OVERWRITE_ASK;
- for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
- iter != m_GlobalSaves.end(); ++iter) {
- if ((*iter)->pcName() == selectedCharacter) {
- QStringList files = getFilesToProcess(*iter);
- foreach (const QString &file, files) {
- QFileInfo fileInfo(file);
- QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
- if (QFile::exists(destinationFile)) {
- if (testOverwrite(overwriteMode, destinationFile)) {
- QFile::remove(destinationFile);
- } else {
- continue;
- }
- }
- if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) {
- qCritical("failed to copy %s to %s",
- fileInfo.absoluteFilePath().toUtf8().constData(),
- destinationFile.toUtf8().constData());
- }
- }
- }
- }
- }
- refreshLocalSaves();
- refreshLocalCharacters();
-}
-
-void TransferSavesDialog::on_moveToGlobalBtn_clicked()
-{
- QString selectedCharacter = ui->localCharacterList->currentItem()->text();
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Move all save games of character \"%1\" to the global location? Please be aware "
- "that this will mess up the running number of save games.").arg(selectedCharacter),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
-
- QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()));
- OverwriteMode overwriteMode = OVERWRITE_ASK;
- for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
- iter != m_LocalSaves.end(); ++iter) {
- if ((*iter)->pcName() == selectedCharacter) {
- QStringList files = getFilesToProcess(*iter);
- foreach (const QString &file, files) {
- QFileInfo fileInfo(file);
- QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
- if (QFile::exists(destinationFile)) {
- if (testOverwrite(overwriteMode, destinationFile)) {
- QFile::remove(destinationFile);
- } else {
- continue;
- }
- }
- if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) {
- qCritical("failed to move %s to %s",
- fileInfo.absoluteFilePath().toUtf8().constData(),
- destinationFile.toUtf8().constData());
- }
- }
- }
- }
- }
- refreshGlobalSaves();
- refreshGlobalCharacters();
- refreshLocalSaves();
- refreshLocalCharacters();
-}
-
-void TransferSavesDialog::on_copyToGlobalBtn_clicked()
-{
- QString selectedCharacter = ui->localCharacterList->currentItem()->text();
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Copy all save games of character \"%1\" to the global location? Please be aware "
- "that this will mess up the running number of save games.").arg(selectedCharacter),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
-
- QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()));
- OverwriteMode overwriteMode = OVERWRITE_ASK;
- for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
- iter != m_LocalSaves.end(); ++iter) {
- if ((*iter)->pcName() == selectedCharacter) {
- QStringList files = getFilesToProcess(*iter);
- foreach (const QString &file, files) {
- QFileInfo fileInfo(file);
- QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
- if (QFile::exists(destinationFile)) {
- if (testOverwrite(overwriteMode, destinationFile)) {
- QFile::remove(destinationFile);
- } else {
- continue;
- }
- }
- if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) {
- qCritical("failed to copy %s to %s",
- fileInfo.absoluteFilePath().toUtf8().constData(),
- destinationFile.toUtf8().constData());
- }
- }
- }
- }
- }
- refreshGlobalSaves();
- refreshGlobalCharacters();
-}
-
-void TransferSavesDialog::on_doneButton_clicked()
-{
- close();
-}
-
-void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText)
-{
- ui->globalSavesList->clear();
- for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
- iter != m_GlobalSaves.end(); ++iter) {
- if ((*iter)->pcName() == currentText) {
- ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName());
- }
- }
-}
-
-void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText)
-{
- ui->localSavesList->clear();
- for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
- iter != m_LocalSaves.end(); ++iter) {
- if ((*iter)->pcName() == currentText) {
- ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName());
- }
- }
-}
+#include "transfersavesdialog.h" +#include "ui_transfersavesdialog.h" +#include "utility.h" +#include <gameinfo.h> +#include <QDir> +#include <QMessageBox> +#include <Shlwapi.h> +#include <shlobj.h> + + +using namespace MOBase; +using namespace MOShared; + + +TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent) + : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile) +{ + ui->setupUi(this); + refreshGlobalSaves(); + refreshLocalSaves(); + refreshGlobalCharacters(); + refreshLocalCharacters(); +} + +TransferSavesDialog::~TransferSavesDialog() +{ + delete ui; +} + + +void TransferSavesDialog::refreshGlobalSaves() +{ + m_GlobalSaves.clear(); + + QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()))); + + QStringList filters; + filters << ToQString(GameInfo::instance().getSaveGameExtension()); + savesDir.setNameFilters(filters); + + QStringList files = savesDir.entryList(QDir::Files, QDir::Time); + + foreach (const QString &filename, files) { + SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); + save->setParent(this); + m_GlobalSaves.push_back(save); + } +} + + +void TransferSavesDialog::refreshLocalSaves() +{ + m_LocalSaves.clear(); + + QDir savesDir(m_Profile.getPath().append("/saves")); + + QStringList filters; + filters << ToQString(GameInfo::instance().getSaveGameExtension()); + savesDir.setNameFilters(filters); + + QStringList files = savesDir.entryList(QDir::Files, QDir::Time); + + foreach (const QString &filename, files) { + SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); + save->setParent(this); + m_LocalSaves.push_back(save); + } +} + + +void TransferSavesDialog::refreshGlobalCharacters() +{ + std::set<QString> characters; + for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->globalCharacterList->clear(); + for (std::set<QString>::const_iterator iter = characters.begin(); + iter != characters.end(); ++iter) { + ui->globalCharacterList->addItem(*iter); + } + if (ui->globalCharacterList->count() > 0) { + ui->globalCharacterList->setCurrentRow(0); + ui->copyToLocalBtn->setEnabled(true); + ui->moveToLocalBtn->setEnabled(true); + } else { + ui->copyToLocalBtn->setEnabled(false); + ui->moveToLocalBtn->setEnabled(false); + } +} + + +void TransferSavesDialog::refreshLocalCharacters() +{ + std::set<QString> characters; + for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->localCharacterList->clear(); + for (std::set<QString>::const_iterator iter = characters.begin(); + iter != characters.end(); ++iter) { + ui->localCharacterList->addItem(*iter); + } + if (ui->localCharacterList->count() > 0) { + ui->localCharacterList->setCurrentRow(0); + ui->copyToGlobalBtn->setEnabled(true); + ui->moveToGlobalBtn->setEnabled(true); + } else { + ui->copyToGlobalBtn->setEnabled(false); + ui->moveToGlobalBtn->setEnabled(false); + } +} + + +bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile) +{ + QMessageBox::StandardButton res = overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No; + if (overwriteMode == OVERWRITE_ASK) { + res = QMessageBox::question(this, tr("Overwrite"), + tr("Overwrite the file \"%1\"").arg(destinationFile), + QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll); + if (res == QMessageBox::YesToAll) { + overwriteMode = OVERWRITE_YES; + res = QMessageBox::Yes; + } else if (res == QMessageBox::NoToAll) { + overwriteMode = OVERWRITE_NO; + res = QMessageBox::No; + } + } + return res == QMessageBox::Yes; +} + +QStringList TransferSavesDialog::getFilesToProcess(const SaveGame *save) +{ + QStringList result = save->attachedFiles(); + result.append(save->fileName()); + return result; +} + +void TransferSavesDialog::on_moveToLocalBtn_clicked() +{ + QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QString destination = m_Profile.getPath().append("/saves"); + OverwriteMode overwriteMode = OVERWRITE_ASK; + + for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = getFilesToProcess(*iter); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to move %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_copyToLocalBtn_clicked() +{ + QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QString destination = m_Profile.getPath().append("/saves"); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = getFilesToProcess(*iter); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to copy %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_moveToGlobalBtn_clicked() +{ + QString selectedCharacter = ui->localCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Move all save games of character \"%1\" to the global location? Please be aware " + "that this will mess up the running number of save games.").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = getFilesToProcess(*iter); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to move %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_copyToGlobalBtn_clicked() +{ + QString selectedCharacter = ui->localCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the global location? Please be aware " + "that this will mess up the running number of save games.").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = getFilesToProcess(*iter); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to copy %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); +} + +void TransferSavesDialog::on_doneButton_clicked() +{ + close(); +} + +void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText) +{ + ui->globalSavesList->clear(); + for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == currentText) { + ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); + } + } +} + +void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText) +{ + ui->localSavesList->clear(); + for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == currentText) { + ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); + } + } +} diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index e8d08a13..923259f6 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -17,67 +17,67 @@ 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 TRANSFERSAVESDIALOG_H
-#define TRANSFERSAVESDIALOG_H
-
-#include "tutorabledialog.h"
-#include "savegamegamebyro.h"
-#include "profile.h"
-
-namespace Ui {
-class TransferSavesDialog;
-}
-
-class TransferSavesDialog : public TutorableDialog
-{
- Q_OBJECT
-
-public:
- explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0);
- ~TransferSavesDialog();
-
-private slots:
-
- void on_moveToLocalBtn_clicked();
-
- void on_doneButton_clicked();
-
- void on_globalCharacterList_currentTextChanged(const QString ¤tText);
-
- void on_localCharacterList_currentTextChanged(const QString ¤tText);
-
- void on_copyToLocalBtn_clicked();
-
- void on_moveToGlobalBtn_clicked();
-
- void on_copyToGlobalBtn_clicked();
-
-private:
-
- enum OverwriteMode {
- OVERWRITE_ASK,
- OVERWRITE_YES,
- OVERWRITE_NO
- };
-
-private:
-
- void refreshGlobalCharacters();
- void refreshLocalCharacters();
- void refreshGlobalSaves();
- void refreshLocalSaves();
- bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
- QStringList getFilesToProcess(const SaveGame *save);
-
-private:
-
- Ui::TransferSavesDialog *ui;
-
- Profile m_Profile;
-
- std::vector<SaveGame*> m_GlobalSaves;
- std::vector<SaveGame*> m_LocalSaves;
-
-};
-
-#endif // TRANSFERSAVESDIALOG_H
+#ifndef TRANSFERSAVESDIALOG_H +#define TRANSFERSAVESDIALOG_H + +#include "tutorabledialog.h" +#include "savegamegamebyro.h" +#include "profile.h" + +namespace Ui { +class TransferSavesDialog; +} + +class TransferSavesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0); + ~TransferSavesDialog(); + +private slots: + + void on_moveToLocalBtn_clicked(); + + void on_doneButton_clicked(); + + void on_globalCharacterList_currentTextChanged(const QString ¤tText); + + void on_localCharacterList_currentTextChanged(const QString ¤tText); + + void on_copyToLocalBtn_clicked(); + + void on_moveToGlobalBtn_clicked(); + + void on_copyToGlobalBtn_clicked(); + +private: + + enum OverwriteMode { + OVERWRITE_ASK, + OVERWRITE_YES, + OVERWRITE_NO + }; + +private: + + void refreshGlobalCharacters(); + void refreshLocalCharacters(); + void refreshGlobalSaves(); + void refreshLocalSaves(); + bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile); + QStringList getFilesToProcess(const SaveGame *save); + +private: + + Ui::TransferSavesDialog *ui; + + Profile m_Profile; + + std::vector<SaveGame*> m_GlobalSaves; + std::vector<SaveGame*> m_LocalSaves; + +}; + +#endif // TRANSFERSAVESDIALOG_H |
