From 6516a6b7c19713de28db7142612f1c095e541317 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 17 Feb 2013 09:26:29 +0100 Subject: - moved shared and uibase libraries to namespaces - bugfix: the "fix mods" function was accessing the save game data incorrectly, causing a crash --- src/activatemodsdialog.h | 104 +-- src/baincomplexinstallerdialog.cpp | 4 + src/baincomplexinstallerdialog.h | 152 ++-- src/categories.cpp | 506 +++++------ src/categoriesdialog.cpp | 446 +++++----- src/categoriesdialog.h | 104 +-- src/directoryrefresher.cpp | 172 ++-- src/directoryrefresher.h | 174 ++-- src/downloadmanager.cpp | 1 + src/dummybsa.cpp | 450 +++++----- src/editexecutablesdialog.cpp | 3 + src/editexecutablesdialog.h | 134 +-- src/executableslist.cpp | 4 + src/executableslist.h | 6 +- src/fomodinstallerdialog.cpp | 3 + src/fomodinstallerdialog.h | 12 +- src/gameinfoimpl.cpp | 66 +- src/gameinfoimpl.h | 40 +- src/installationmanager.cpp | 4 + src/installationmanager.h | 28 +- src/installdialog.cpp | 575 ++++++------- src/installdialog.h | 216 ++--- src/loadmechanism.cpp | 536 ++++++------ src/main.cpp | 4 + src/mainwindow.cpp | 5 +- src/mainwindow.h | 30 +- src/mainwindow.ui | 8 +- src/moapplication.cpp | 122 +-- src/modinfo.cpp | 4 + src/modinfo.h | 34 +- src/modinfodialog.cpp | 3 + src/modinfodialog.h | 8 +- src/modlist.cpp | 3 + src/motddialog.cpp | 12 +- src/nexusdialog.cpp | 3 + src/nexusdialog.h | 2 +- src/nexusinterface.cpp | 3 + src/nexusinterface.h | 616 ++++++------- src/nexusview.cpp | 4 + src/nxmaccessmanager.cpp | 272 +++--- src/nxmurl.cpp | 30 +- src/overwriteinfodialog.cpp | 465 +++++----- src/pluginlist.cpp | 1667 ++++++++++++++++++------------------ src/pluginlist.h | 4 +- src/profile.cpp | 1350 ++++++++++++++--------------- src/profilesdialog.cpp | 514 +++++------ src/profilesdialog.h | 150 ++-- src/report.cpp | 64 +- src/savegamegamebryo.cpp | 653 +++++++------- src/savegameinfowidget.cpp | 86 +- src/savetextasdialog.cpp | 2 +- src/selfupdater.cpp | 876 +++++++++---------- src/selfupdater.h | 252 +++--- src/settings.cpp | 986 ++++++++++----------- src/settings.h | 408 ++++----- src/settingsdialog.cpp | 264 +++--- src/settingsdialog.h | 122 +-- src/shared/appconfig.cpp | 41 +- src/shared/appconfig.h | 42 +- src/shared/directoryentry.cpp | 1601 +++++++++++++++++----------------- src/shared/directoryentry.h | 42 +- src/shared/error_report.cpp | 41 +- src/shared/error_report.h | 46 +- src/shared/fallout3info.cpp | 43 +- src/shared/fallout3info.h | 42 +- src/shared/falloutnvinfo.cpp | 43 +- src/shared/falloutnvinfo.h | 42 +- src/shared/gameinfo.cpp | 41 +- src/shared/gameinfo.h | 42 +- src/shared/inject.cpp | 42 +- src/shared/inject.h | 20 +- src/shared/oblivioninfo.cpp | 43 +- src/shared/oblivioninfo.h | 42 +- src/shared/skyriminfo.cpp | 43 +- src/shared/skyriminfo.h | 42 +- src/shared/util.cpp | 41 +- src/shared/util.h | 42 +- src/shared/windows_error.cpp | 42 +- src/shared/windows_error.h | 42 +- src/singleinstance.cpp | 176 ++-- src/spawn.cpp | 184 ++-- src/syncoverwritedialog.cpp | 258 +++--- src/syncoverwritedialog.h | 72 +- src/transfersavesdialog.cpp | 628 +++++++------- src/transfersavesdialog.h | 128 +-- 85 files changed, 8434 insertions(+), 8243 deletions(-) (limited to 'src') 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 . */ -#ifndef ACTIVATEMODSDIALOG_H -#define ACTIVATEMODSDIALOG_H - -#include "tutorabledialog.h" -#include -#include - -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 > &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 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 getESPsToActivate(); - -private slots: - void on_buttonBox_accepted(); - -private: - Ui::ActivateModsDialog *ui; -}; - -#endif // ACTIVATEMODSDIALOG_H +#ifndef ACTIVATEMODSDIALOG_H +#define ACTIVATEMODSDIALOG_H + +#include "tutorabledialog.h" +#include +#include + +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 > &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 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 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 . #include + +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 . */ -#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 . */ -#include "categories.h" -#include -#include "report.h" -#include -#include -#include -#include -#include - - -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 cells = line.split('|'); - if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", - lineNum, line.constData(), cells.count()); - } else { - std::vector nexusIDs; - if (cells[2].length() > 0) { - QList nexusIDStrings = cells[2].split(','); - for (QList::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(2, 28, 87), 0); -} - - -void CategoryFactory::setParents() -{ - for (std::vector::iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - iter->m_HasChildren = false; - } - - for (std::vector::const_iterator categoryIter = m_Categories.begin(); - categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->m_ParentID != 0) { - std::map::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::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 &nexusIDs, int parentID) -{ - int index = m_Categories.size(); - m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); - for (std::vector::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(1, 51), 0); - addCategory(2, "Armour", MakeVector(1, 54), 0); - addCategory(3, "Audio", MakeVector(1, 61), 0); - addCategory(5, "Clothing", MakeVector(1, 60), 0); - addCategory(6, "Collectables", MakeVector(1, 92), 0); - addCategory(7, "Creatures", MakeVector(2, 83, 65), 0); - addCategory(8, "Factions", MakeVector(1, 25), 0); - addCategory(9, "Gameplay", MakeVector(1, 24), 0); - addCategory(10, "Hair", MakeVector(1, 26), 0); - addCategory(11, "Items", MakeVector(2, 27, 85), 0); - addCategory(19, "Weapons", MakeVector(2, 36, 55), 11); - addCategory(12, "Locations", MakeVector(7, 22, 30, 70, 88, 89, 90, 91), 0); - addCategory(4, "Cities", MakeVector(1, 53), 12); - addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); - addCategory(21, "Models & Textures", MakeVector(1, 29), 0); - addCategory(13, "NPCs", MakeVector(1, 33), 0); - addCategory(14, "Patches", MakeVector(2, 79, 84), 0); - addCategory(15, "Quests", MakeVector(1, 35), 0); - addCategory(16, "Races & Classes", MakeVector(1, 34), 0); - addCategory(22, "Skills", MakeVector(1, 73), 0); - addCategory(17, "UI", MakeVector(1, 42), 0); - addCategory(18, "Visuals", MakeVector(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::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::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::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 +#include "report.h" +#include +#include +#include +#include +#include + + +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 cells = line.split('|'); + if (cells.count() != 4) { + qCritical("invalid category line %d: %s (%d cells)", + lineNum, line.constData(), cells.count()); + } else { + std::vector nexusIDs; + if (cells[2].length() > 0) { + QList nexusIDStrings = cells[2].split(','); + for (QList::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(2, 28, 87), 0); +} + + +void CategoryFactory::setParents() +{ + for (std::vector::iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + iter->m_HasChildren = false; + } + + for (std::vector::const_iterator categoryIter = m_Categories.begin(); + categoryIter != m_Categories.end(); ++categoryIter) { + if (categoryIter->m_ParentID != 0) { + std::map::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::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 &nexusIDs, int parentID) +{ + int index = m_Categories.size(); + m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); + for (std::vector::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(1, 51), 0); + addCategory(2, "Armour", MakeVector(1, 54), 0); + addCategory(3, "Audio", MakeVector(1, 61), 0); + addCategory(5, "Clothing", MakeVector(1, 60), 0); + addCategory(6, "Collectables", MakeVector(1, 92), 0); + addCategory(7, "Creatures", MakeVector(2, 83, 65), 0); + addCategory(8, "Factions", MakeVector(1, 25), 0); + addCategory(9, "Gameplay", MakeVector(1, 24), 0); + addCategory(10, "Hair", MakeVector(1, 26), 0); + addCategory(11, "Items", MakeVector(2, 27, 85), 0); + addCategory(19, "Weapons", MakeVector(2, 36, 55), 11); + addCategory(12, "Locations", MakeVector(7, 22, 30, 70, 88, 89, 90, 91), 0); + addCategory(4, "Cities", MakeVector(1, 53), 12); + addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); + addCategory(21, "Models & Textures", MakeVector(1, 29), 0); + addCategory(13, "NPCs", MakeVector(1, 33), 0); + addCategory(14, "Patches", MakeVector(2, 79, 84), 0); + addCategory(15, "Quests", MakeVector(1, 35), 0); + addCategory(16, "Races & Classes", MakeVector(1, 34), 0); + addCategory(22, "Skills", MakeVector(1, 73), 0); + addCategory(17, "UI", MakeVector(1, 42), 0); + addCategory(18, "Visuals", MakeVector(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::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::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::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 . */ -#include "categoriesdialog.h" -#include "ui_categoriesdialog.h" -#include "categories.h" -#include "utility.h" -#include -#include -#include -#include - - -class NewIDValidator : public QIntValidator { -public: - NewIDValidator(const std::set &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 &m_UsedIDs; -}; - - -class ExistingIDValidator : public QIntValidator { -public: - ExistingIDValidator(const std::set &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 &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(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 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::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 idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, category.m_ID); - - QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); - QScopedPointer nexusIDItem(new QTableWidgetItem(VectorJoin(category.m_NexusIDs, ","))); - QScopedPointer 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 +#include +#include +#include + + +class NewIDValidator : public QIntValidator { +public: + NewIDValidator(const std::set &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 &m_UsedIDs; +}; + + +class ExistingIDValidator : public QIntValidator { +public: + ExistingIDValidator(const std::set &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 &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(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 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::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 idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, category.m_ID); + + QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); + QScopedPointer 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 . */ -#ifndef CATEGORIESDIALOG_H -#define CATEGORIESDIALOG_H - -#include "tutorabledialog.h" -#include - -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 m_IDs; - -}; - -#endif // CATEGORIESDIALOG_H - +#ifndef CATEGORIESDIALOG_H +#define CATEGORIESDIALOG_H + +#include "tutorabledialog.h" +#include + +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 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 . */ -#include "directoryrefresher.h" -#include "utility.h" -#include "report.h" -#include -#include - - -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 > &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 >::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 +#include + + +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 > &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 >::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 . */ -#ifndef DIRECTORYREFRESHER_H -#define DIRECTORYREFRESHER_H - -#include -#include -#include -#include -#include - - -/** - * @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 > &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 > m_Mods; - DirectoryEntry *m_DirectoryStructure; - QMutex m_RefreshLock; - -}; - -#endif // DIRECTORYREFRESHER_H +#ifndef DIRECTORYREFRESHER_H +#define DIRECTORYREFRESHER_H + +#include +#include +#include +#include +#include + + +/** + * @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 > &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 > 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 . 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 . */ -#include "dummybsa.h" -#include -#include -#define WIN32_LEAN_AND_MEAN -#include - - -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(tolower(fileName[i])); - if (fileNameLower[i] == '\\') { - fileNameLower[i] = '/'; - } - } - fileNameLower[i] = '\0'; - - unsigned char *fileNameLowerU = reinterpret_cast(fileNameLower); - - char* ext = strrchr(fileNameLower, '.'); - if (ext == NULL) { - ext = fileNameLower + strlen(fileNameLower); - } - unsigned char *extU = reinterpret_cast(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(genHashInt( - fileNameLowerU + 1, extU - 2)); - temp += static_cast(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(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(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(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 +#include +#define WIN32_LEAN_AND_MEAN +#include + + +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(tolower(fileName[i])); + if (fileNameLower[i] == '\\') { + fileNameLower[i] = '/'; + } + } + fileNameLower[i] = '\0'; + + unsigned char *fileNameLowerU = reinterpret_cast(fileNameLower); + + char* ext = strrchr(fileNameLower, '.'); + if (ext == NULL) { + ext = fileNameLower + strlen(fileNameLower); + } + unsigned char *extU = reinterpret_cast(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(genHashInt( + fileNameLowerU + 1, extU - 2)); + temp += static_cast(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(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(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(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 . #include +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 . */ -#ifndef EDITEXECUTABLESDIALOG_H -#define EDITEXECUTABLESDIALOG_H - -#include "tutorabledialog.h" -#include -#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 +#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 . #include +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 . #include +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 &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 . */ -#include "gameinfoimpl.h" -#include -#include -#include - - -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 +#include +#include + + +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 . */ -#ifndef GAMEINFOIMPL_H -#define GAMEINFOIMPL_H - - -#include -#include - - -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 +#include + + +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 . #include +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 . * 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 m_Installers; + std::vector m_Installers; std::set 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 . */ -#include "installdialog.h" -#include "ui_installdialog.h" - -#include "report.h" -#include "utility.h" -#include "installationtester.h" - -#include -#include -#include - - -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("editName"); - editName->setText(modName); - - m_Tree = findChild("treeContent"); - - m_ProblemLabel = findChild("problemLabel"); - - connect(m_Tree, SIGNAL(changed()), this, SLOT(treeChanged())); - - updatePreview(); -} - -InstallDialog::~InstallDialog() -{ - delete ui; -} - - -QString InstallDialog::getModName() const -{ - QLineEdit *editName = findChild("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("")); - - 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, ""); - 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 +#include +#include + + +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("editName"); + editName->setText(modName); + + m_Tree = findChild("treeContent"); + + m_ProblemLabel = findChild("problemLabel"); + + connect(m_Tree, SIGNAL(changed()), this, SLOT(treeChanged())); + + updatePreview(); +} + +InstallDialog::~InstallDialog() +{ + delete ui; +} + + +QString InstallDialog::getModName() const +{ + QLineEdit *editName = findChild("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("")); + + 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, ""); + 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 . */ -#ifndef INSTALLDIALOG_H -#define INSTALLDIALOG_H - -#include "mytree.h" -#include "archivetree.h" -#include "tutorabledialog.h" -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - - -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 +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + + +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 . */ -#include "loadmechanism.h" -#include "utility.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include - - -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 +#include +#include +#include +#include +#include +#include +#include + + +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 . #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 . #include +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(selectedItem->data(Qt::UserRole)); + const SaveGameGamebryo *save = getSaveGame(selectedItem); // collect the list of missing plugins std::map > 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 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 m_DiagnosisPlugins; + std::vector 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; } - + - Name filter + Namefilter @@ -908,7 +908,7 @@ p, li { white-space: pre-wrap; } - + Filter @@ -1205,7 +1205,7 @@ Right now this has very limited functionality - LineEditClear + MOBase::LineEditClear QLineEdit
lineeditclear.h
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 . */ -#include "moapplication.h" -#include "report.h" -#include "utility.h" -#include -#include -#include - - -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 +#include +#include + + +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 . #include +using namespace MOBase; +using namespace MOShared; + + std::vector ModInfo::s_Collection; std::map ModInfo::s_ModsByName; std::map 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 . * 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 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 . 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 . #include +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 . #include +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 . 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 . */ -#ifndef NEXUSINTERFACE_H -#define NEXUSINTERFACE_H - - -#include "nxmaccessmanager.h" - -#include "utility.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -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 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 &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 &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 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 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::iterator iter); - -private: - - static NexusInterface *s_Instance; - - QNetworkDiskCache *m_DiskCache; - - NXMAccessManager *m_AccessManager; - - std::list m_ActiveRequest; - QQueue m_RequestQueue; - - QString m_NMMVersion; - -}; - -#endif // NEXUSINTERFACE_H +#ifndef NEXUSINTERFACE_H +#define NEXUSINTERFACE_H + + +#include "nxmaccessmanager.h" + +#include "utility.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 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 &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 &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 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 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::iterator iter); + +private: + + static NexusInterface *s_Instance; + + QNetworkDiskCache *m_DiskCache; + + NXMAccessManager *m_AccessManager; + + std::list m_ActiveRequest; + QQueue 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 . #include #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 . */ -#include "nxmaccessmanager.h" -#include "nxmurl.h" -#include "report.h" -#include "utility.h" -#include "selfupdater.h" -#include -#include -#include -#include -#include -#include - - -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 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 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 +#include +#include +#include +#include +#include + + +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 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 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 . */ -#include "nxmurl.h" -#include -#include -#include - -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 +#include +#include + +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 . */ -#include "overwriteinfodialog.h" -#include "ui_overwriteinfodialog.h" -#include "report.h" -#include "utility.h" -#include -#include -#include - - -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 +#include +#include + + +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 . */ -#include "pluginlist.h" -#include "report.h" -#include "inject.h" -#include -#include "settings.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - - - -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 primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - - m_CurrentProfile = profileName; - - std::vector 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::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { - std::map::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::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(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::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::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::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::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 availableESPs; - for (std::vector::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 primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - for (std::vector::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::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::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 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 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::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 lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair &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(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(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(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 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 &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::const_iterator iter = rows.begin(); - iter != rows.end(); ++iter) { - if (m_ESPs[*iter].m_Priority < newPriority) { - --newPriority; - break; - } - } - - for (std::vector::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 sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap 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(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(obj); - QKeyEvent *keyEvent = static_cast(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(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 +#include "settings.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +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 primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + + m_CurrentProfile = profileName; + + std::vector 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::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { + std::map::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::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(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::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::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::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::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 availableESPs; + for (std::vector::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 primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + for (std::vector::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::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::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 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 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::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 lockedLoadOrder; + std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), + [&lockedLoadOrder] (const std::pair &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(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(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(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 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 &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::const_iterator iter = rows.begin(); + iter != rows.end(); ++iter) { + if (m_ESPs[*iter].m_Priority < newPriority) { + --newPriority; + break; + } + } + + for (std::vector::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 sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap 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(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(obj); + QKeyEvent *keyEvent = static_cast(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(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 . */ -#include "profile.h" -#include "report.h" -#include "gameinfo.h" -#include "windows_error.h" -#include "dummybsa.h" -#include "modinfo.h" -#include -#include -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - - -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 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(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(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(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } - } -} - - -std::vector > Profile::getActiveMods() -{ - std::vector > result; - for (std::vector::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 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)(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 Profile::splitDZString(const wchar_t *buffer) const -{ - std::vector 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 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 sections = splitDZString(buffer.data()); - - // now iterate over all sections and retrieve a list of keys in each - for (std::vector::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 keys = splitDZString(buffer.data()); - - for (std::vector::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 iniTweaks = modInfo->getIniTweaks(); - for (std::vector::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 +#include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +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 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(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(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(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; + } + } +} + + +std::vector > Profile::getActiveMods() +{ + std::vector > result; + for (std::vector::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 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)(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 Profile::splitDZString(const wchar_t *buffer) const +{ + std::vector 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 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 sections = splitDZString(buffer.data()); + + // now iterate over all sections and retrieve a list of keys in each + for (std::vector::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 keys = splitDZString(buffer.data()); + + for (std::vector::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 iniTweaks = modInfo->getIniTweaks(); + for (std::vector::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 . */ -#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 -#include -#include -#include -#include -#include -#include - - -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("profilesList"); - - QDirIterator profileIter(profilesDir); - - while (profileIter.hasNext()) { - profileIter.next(); - addItem(profileIter.filePath()); - } - - QCheckBox *invalidationBox = findChild("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("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("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("profilesList"); - - try { - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - 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("profilesList"); - - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - - // 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("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(); - 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("invalidationBox"); - QCheckBox *localSavesBox = findChild("localSavesBox"); - QPushButton *copyButton = findChild("copyProfileButton"); - QPushButton *removeButton = findChild("removeProfileButton"); - QPushButton *transferButton = findChild("transferButton"); - - if (current != NULL) { - const Profile ¤tProfile = current->data(Qt::UserRole).value(); - - 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(); - 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(); - 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 +#include +#include +#include +#include +#include +#include + + +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("profilesList"); + + QDirIterator profileIter(profilesDir); + + while (profileIter.hasNext()) { + profileIter.next(); + addItem(profileIter.filePath()); + } + + QCheckBox *invalidationBox = findChild("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("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("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("profilesList"); + + try { + const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + 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("profilesList"); + + const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + + // 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("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(); + 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("invalidationBox"); + QCheckBox *localSavesBox = findChild("localSavesBox"); + QPushButton *copyButton = findChild("copyProfileButton"); + QPushButton *removeButton = findChild("removeProfileButton"); + QPushButton *transferButton = findChild("transferButton"); + + if (current != NULL) { + const Profile ¤tProfile = current->data(Qt::UserRole).value(); + + 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(); + 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(); + 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 . */ -#ifndef PROFILESDIALOG_H -#define PROFILESDIALOG_H - -#include "tutorabledialog.h" -#include -#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 +#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 . */ -#include "report.h" -#include "utility.h" -#include -#include -#include - -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 +#include +#include + + +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 . */ -#include "savegamegamebyro.h" -#include -#include -#include -#include "gameinfo.h" -#include -#include -#include - - -template -void FileRead(QFile &file, T &value) -{ - int read = file.read(reinterpret_cast(&value), sizeof(T)); - if (read != sizeof(T)) { - throw std::runtime_error("unexpected end of file"); - } -} - - -template -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(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(saveFile); // header size - FileSkip(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(temp); - - m_PCLocation = ReadFOSString(saveFile, false); - ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss - ReadFOSString(saveFile, false); // race name (i.e. BretonRace) - - - FileSkip(saveFile); // ??? - FileSkip(saveFile, 2); // ??? - FileSkip(saveFile, 8); // filetime - -// FileSkip(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 buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(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(saveFile); // form version - FileSkip(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(saveFile); FileRead(saveFile, versionMinor); - FileSkip(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(saveFile); // game days - FileSkip(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 buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(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(saveFile, ignore); // unknown - } - if (newVegas) { - ignore = 0x00; - // in new vegas there is another block of uninteresting (?) information - FileSkip(saveFile); // 0x7c - while (ignore != 0x7c) { - FileRead(saveFile, ignore); // unknown - } - } - - unsigned long width, height; - FileRead(saveFile, width); - FileSkip(saveFile); // 0x7c - FileRead(saveFile, height); - FileSkip(saveFile); // 0x7c - - FileRead(saveFile, m_SaveNumber); - FileSkip(saveFile); // 0x7c - - m_PCName = ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - long Level; - FileRead(saveFile, Level); - m_PCLevel = Level; - FileSkip(saveFile); // 0x7c - - m_PCLocation = ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - ReadFOSString(saveFile, true); // playtime - - FileSkip(saveFile); - - QScopedArrayPointer buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(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(saveFile, 5); // unknown - - unsigned char pluginCount = 0; - FileRead(saveFile, pluginCount); - FileSkip(saveFile); // 0x7c - - for (int i = 0; i < pluginCount; ++i) { - QString name = ReadFOSString(saveFile, true); - m_Plugins.push_back(name); - FileSkip(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(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast(modified.date().month()); - m_CreationTime.wYear =static_cast( modified.date().year()); - - m_CreationTime.wHour = static_cast(modified.time().hour()); - m_CreationTime.wMinute = static_cast(modified.time().minute()); - m_CreationTime.wSecond = static_cast(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast(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 +#include +#include +#include "gameinfo.h" +#include +#include +#include + + +using namespace MOShared; + + +template +void FileRead(QFile &file, T &value) +{ + int read = file.read(reinterpret_cast(&value), sizeof(T)); + if (read != sizeof(T)) { + throw std::runtime_error("unexpected end of file"); + } +} + + +template +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(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(saveFile); // header size + FileSkip(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(temp); + + m_PCLocation = ReadFOSString(saveFile, false); + ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss + ReadFOSString(saveFile, false); // race name (i.e. BretonRace) + + + FileSkip(saveFile); // ??? + FileSkip(saveFile, 2); // ??? + FileSkip(saveFile, 8); // filetime + +// FileSkip(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 buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(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(saveFile); // form version + FileSkip(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(saveFile); FileRead(saveFile, versionMinor); + FileSkip(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(saveFile); // game days + FileSkip(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 buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(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(saveFile, ignore); // unknown + } + if (newVegas) { + ignore = 0x00; + // in new vegas there is another block of uninteresting (?) information + FileSkip(saveFile); // 0x7c + while (ignore != 0x7c) { + FileRead(saveFile, ignore); // unknown + } + } + + unsigned long width, height; + FileRead(saveFile, width); + FileSkip(saveFile); // 0x7c + FileRead(saveFile, height); + FileSkip(saveFile); // 0x7c + + FileRead(saveFile, m_SaveNumber); + FileSkip(saveFile); // 0x7c + + m_PCName = ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + long Level; + FileRead(saveFile, Level); + m_PCLevel = Level; + FileSkip(saveFile); // 0x7c + + m_PCLocation = ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + ReadFOSString(saveFile, true); // playtime + + FileSkip(saveFile); + + QScopedArrayPointer buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(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(saveFile, 5); // unknown + + unsigned char pluginCount = 0; + FileRead(saveFile, pluginCount); + FileSkip(saveFile); // 0x7c + + for (int i = 0; i < pluginCount; ++i) { + QString name = ReadFOSString(saveFile, true); + m_Plugins.push_back(name); + FileSkip(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(modified.date().day()); + m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); + m_CreationTime.wMonth = static_cast(modified.date().month()); + m_CreationTime.wYear =static_cast( modified.date().year()); + + m_CreationTime.wHour = static_cast(modified.time().hour()); + m_CreationTime.wMinute = static_cast(modified.time().minute()); + m_CreationTime.wSecond = static_cast(modified.time().second()); + m_CreationTime.wMilliseconds = static_cast(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 . */ -#include "savegameinfowidget.h" -#include "ui_savegameinfowidget.h" -#include "utility.h" -#include - - -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 + + +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 . */ -#include "selfupdater.h" -#include "utility.h" -#include "installationmanager.h" -#include "report.h" -#include "messagedialog.h" -#include "downloadmanager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -typedef Archive* (*CreateArchiveType)(); - - -template T resolveFunction(QLibrary &lib, const char *name) -{ - T temp = reinterpret_cast(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(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(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(this, &SelfUpdater::updateProgress), - new MethodCallback(this, &SelfUpdater::updateProgressFile), - new MethodCallback(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()) { - 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +typedef Archive* (*CreateArchiveType)(); + + +template T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(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(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(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(this, &SelfUpdater::updateProgress), + new MethodCallback(this, &SelfUpdater::updateProgressFile), + new MethodCallback(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()) { + 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 . */ -#ifndef SELFUPDATER_H -#define SELFUPDATER_H - - -#include "nexusinterface.h" -#include -#include - -#include -#include -#include -#include - - -/** - * @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 +#include + +#include +#include +#include +#include + + +/** + * @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 . */ -#include "settings.h" - -#include "settingsdialog.h" -#include "utility.h" -#include "helper.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -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("hideUncheckedBox"); - QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); - QComboBox *mechanismBox = dialog.findChild("mechanismBox"); - - QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); - QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); - QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); - - QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); - - QComboBox *languageBox = dialog.findChild("languageBox"); - QComboBox *styleBox = dialog.findChild("styleBox"); - QComboBox *logLevelBox = dialog.findChild("logLevelBox"); - QCheckBox *handleNXMBox = dialog.findChild("handleNXMBox"); - - QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); - QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); - QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); - - QCheckBox *preferIntegratedCheckBox = dialog.findChild("preferIntegratedBox"); - QCheckBox *preferExternalBox = dialog.findChild("preferExternalBox"); - QCheckBox *quickInstallerBox = dialog.findChild("quickInstallerBox"); - QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); - - QListWidget *pluginsList = dialog.findChild("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 +#include +#include + +#include +#include +#include +#include +#include +#include + + +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("hideUncheckedBox"); + QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); + QComboBox *mechanismBox = dialog.findChild("mechanismBox"); + + QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); + QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); + QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); + + QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); + + QComboBox *languageBox = dialog.findChild("languageBox"); + QComboBox *styleBox = dialog.findChild("styleBox"); + QComboBox *logLevelBox = dialog.findChild("logLevelBox"); + QCheckBox *handleNXMBox = dialog.findChild("handleNXMBox"); + + QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); + QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); + QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); + + QCheckBox *preferIntegratedCheckBox = dialog.findChild("preferIntegratedBox"); + QCheckBox *preferExternalBox = dialog.findChild("preferExternalBox"); + QCheckBox *quickInstallerBox = dialog.findChild("quickInstallerBox"); + QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); + + QListWidget *pluginsList = dialog.findChild("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 . */ -#ifndef WORKAROUNDS_H -#define WORKAROUNDS_H - -#include "loadmechanism.h" - -#include - -#include -#include -#include - - -/** - * 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 &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 m_Plugins; - - QMap > m_PluginSettings; - -}; - -#endif // WORKAROUNDS_H +#ifndef WORKAROUNDS_H +#define WORKAROUNDS_H + +#include "loadmechanism.h" + +#include + +#include +#include +#include + + +/** + * 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 &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 m_Plugins; + + QMap > 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 . */ -#include "settingsdialog.h" -#include "ui_settingsdialog.h" -#include "categoriesdialog.h" -#include "helper.h" -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include - - -SettingsDialog::SettingsDialog(QWidget *parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) -{ - ui->setupUi(this); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -void SettingsDialog::addPlugins(const std::vector &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("usernameEdit"); - QLineEdit *passwordEdit = findChild("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 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(current->data(Qt::UserRole).value()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QMap 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 +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include + + +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 &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("usernameEdit"); + QLineEdit *passwordEdit = findChild("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 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(current->data(Qt::UserRole).value()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QMap 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 . */ -#ifndef WORKAROUNDDIALOG_H -#define WORKAROUNDDIALOG_H - -#include "tutorabledialog.h" -#include -#include -#include - -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 &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 +#include +#include + +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 &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 . -*/ - +/* +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 . +*/ + #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 . -*/ - +/* +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 . +*/ + #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 . -*/ - -#include "directoryentry.h" -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include "error_report.h" -#include "util.h" -#include "windows_error.h" -#include - - - -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, boost::shared_ptr 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::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::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 m_Origins; - std::map m_OriginsNameMap; - std::map m_OriginsPriorityMap; - -}; - - -// -// FilesOrigin -// - - -void FilesOrigin::enable(bool enabled) -{ - if (!enabled) { - std::set 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 FilesOrigin::getFiles() const -{ - std::vector 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::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::iterator currentIter = m_Alternatives.begin(); - for (std::vector::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::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, boost::shared_ptr 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::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 &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::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 DirectoryEntry::getFiles() const -{ - std::vector 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::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - const_cast(*iter).sortOrigins(); - } - for (std::vector::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - (*iter)->sortOrigins(); - } -} -*/ - -DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const -{ - for (std::vector::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::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::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) - : 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 &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 . +*/ + +#include "directoryentry.h" +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include "error_report.h" +#include "util.h" +#include "windows_error.h" +#include + +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, boost::shared_ptr 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::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::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 m_Origins; + std::map m_OriginsNameMap; + std::map m_OriginsPriorityMap; + +}; + + +// +// FilesOrigin +// + + +void FilesOrigin::enable(bool enabled) +{ + if (!enabled) { + std::set 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 FilesOrigin::getFiles() const +{ + std::vector 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::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::iterator currentIter = m_Alternatives.begin(); + for (std::vector::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::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, boost::shared_ptr 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::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 &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::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 DirectoryEntry::getFiles() const +{ + std::vector 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::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + const_cast(*iter).sortOrigins(); + } + for (std::vector::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + (*iter)->sortOrigins(); + } +} +*/ + +DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const +{ + for (std::vector::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::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::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) + : 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 &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 . -*/ - +/* +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 . +*/ + #ifndef DIRECTORYENTRY_H #define DIRECTORYENTRY_H @@ -31,6 +31,8 @@ along with Mod Organizer. If not, see . #include #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 . -*/ - +/* +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 . +*/ + #include "error_report.h" #include #include +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 . -*/ - +/* +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 . +*/ + #pragma once #include @@ -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 . -*/ - +/* +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 . +*/ + #include "fallout3info.h" #include "util.h" #include @@ -28,6 +28,8 @@ along with Mod Organizer. If not, see . #include #include +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 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 . -*/ - +/* +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 . +*/ + #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 . -*/ - +/* +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 . +*/ + #include "falloutnvinfo.h" #include "util.h" #include @@ -28,6 +28,8 @@ along with Mod Organizer. If not, see . #include #include +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 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 . -*/ - +/* +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 . +*/ + #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 . -*/ - +/* +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 . +*/ + #include "gameinfo.h" #include "windows_error.h" @@ -31,6 +31,8 @@ along with Mod Organizer. If not, see . #include #include +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 . -*/ - +/* +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 . +*/ + #ifndef GAMEINFO_H #define GAMEINFO_H @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include +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 . -*/ - +/* +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 . +*/ + #include "inject.h" /*#if defined UNICODE && !defined _UNICODE #define _UNICODE 1 @@ -31,6 +31,8 @@ along with Mod Organizer. If not, see . #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 . */ -#pragma once - -#define WIN32_LEAN_AND_MEAN -#include - -#include - -void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +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 . -*/ - +/* +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 . +*/ + #include "oblivioninfo.h" #include #include @@ -28,6 +28,8 @@ along with Mod Organizer. If not, see . #include #include +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 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 . -*/ - +/* +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 . +*/ + #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 . -*/ - +/* +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 . +*/ + #include "skyriminfo.h" #include "util.h" @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see . #include #include +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 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 . -*/ - +/* +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 . +*/ + #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 . -*/ - +/* +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 . +*/ + #include "util.h" #include "windows_error.h" #include #include +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 . -*/ - +/* +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 . +*/ + #ifndef UTIL_H #define UTIL_H @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include +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 . -*/ - +/* +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 . +*/ + #include "windows_error.h" #include +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 . -*/ - +/* +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 . +*/ + #ifndef WINDOWS_ERROR_H #define WINDOWS_ERROR_H @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include +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 . */ -#include "singleinstance.h" -#include "report.h" -#include "utility.h" -#include - -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 + +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 . */ -#include "spawn.h" -#include "report.h" -#include "utility.h" -#include -#include -#include -#include -#include - - -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 +#include +#include +#include +#include + + +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 . */ -#include "syncoverwritedialog.h" -#include "ui_syncoverwritedialog.h" -#include -#include -#include -#include -#include -#include -#include - - -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(""), -1); - if (entry != NULL) { - const std::vector &alternatives = entry->getAlternatives(); - for (std::vector::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("")); - 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(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 +#include +#include +#include +#include +#include +#include + + +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(""), -1); + if (entry != NULL) { + const std::vector &alternatives = entry->getAlternatives(); + for (std::vector::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("")); + 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(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 . */ -#ifndef SYNCOVERWRITEDIALOG_H -#define SYNCOVERWRITEDIALOG_H - - -#include -#include "tutorabledialog.h" -#include - - -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 +#include "tutorabledialog.h" +#include + + +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 . */ -#include "transfersavesdialog.h" -#include "ui_transfersavesdialog.h" -#include "utility.h" -#include -#include -#include -#include -#include - - -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 characters; - for (std::vector::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->globalCharacterList->clear(); - for (std::set::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 characters; - for (std::vector::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->localCharacterList->clear(); - for (std::set::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::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::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::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::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::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::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 +#include +#include +#include +#include + + +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 characters; + for (std::vector::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->globalCharacterList->clear(); + for (std::set::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 characters; + for (std::vector::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->localCharacterList->clear(); + for (std::set::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::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::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::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::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::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::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 . */ -#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 m_GlobalSaves; - std::vector 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 m_GlobalSaves; + std::vector m_LocalSaves; + +}; + +#endif // TRANSFERSAVESDIALOG_H -- cgit v1.3.1