From cffd9eb4e21f0ddcddca68d8eb0f1c80c8ac6ae1 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 5 Oct 2013 14:20:48 +0200 Subject: - hook.dll now doesn't inject to certain applications (currently steam, chrome and firefox) - versioning system improved. Will now report "downgrades" for mods and support a different versioning system (requires manual switch) - updates can now be ignored until a new version is uploaded - new splash screen - bugfix: a few memory leaks (shouldn't account for much) - bugfix: result of GetModuleHandle wasn't zero-terminated in some cases --- src/shared/gameinfo.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/shared/gameinfo.h') diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 3e022ef4..14f52f05 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -176,9 +176,11 @@ private: static bool identifyGame(const std::wstring &omoDirectory, const std::wstring &searchPath); std::wstring getSpecialPath(LPCWSTR name) const; + static void cleanup(); + private: - static GameInfo* s_Instance; + static GameInfo *s_Instance; std::wstring m_MyGamesDirectory; -- cgit v1.3.1 From 8c0b309bdee2527fab66b00b34171ef18f5055bf Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 25 Oct 2013 14:49:42 +0200 Subject: - Option to choose edition of the game. Currently only relevant for FO3 because FO3 GOTY is its own app - applications that aren't in the executables list can again be started from the command line - references to missing categories are now removed from mods - bugfix: integrated fomod installer didn't update description and picture on some constellations of Windows version and theme --- src/ModOrganizer.pro | 20 ++++++++++---------- src/main.cpp | 21 +++++++++++++++++++++ src/mainwindow.cpp | 2 +- src/modinfo.cpp | 1 - src/modlist.cpp | 14 ++++++++++---- src/settings.cpp | 2 +- src/shared/fallout3info.cpp | 11 +++++++++-- src/shared/fallout3info.h | 3 ++- src/shared/falloutnvinfo.cpp | 2 +- src/shared/falloutnvinfo.h | 2 +- src/shared/gameinfo.cpp | 6 ++++++ src/shared/gameinfo.h | 3 ++- src/shared/oblivioninfo.cpp | 2 +- src/shared/oblivioninfo.h | 2 +- src/shared/skyriminfo.cpp | 2 +- src/shared/skyriminfo.h | 2 +- src/version.rc | 4 ++-- 17 files changed, 70 insertions(+), 29 deletions(-) (limited to 'src/shared/gameinfo.h') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index c8bfe6df..f35958f8 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -3,21 +3,21 @@ CONFIG += ordered SUBDIRS = bsatk \ - shared \ - uibase \ - organizer \ - hookdll \ - archive \ - helper \ + shared \ + uibase \ + organizer \ + hookdll \ + archive \ + helper \ plugins \ proxydll \ nxmhandler \ - BossDummy \ - pythonRunner \ - esptk + BossDummy \ + pythonRunner \ + esptk hookdll.depends = shared -organizer.depends = shared, uibase, plugins +organizer.depends = shared uibase plugins CONFIG(debug, debug|release) { DESTDIR = outputd diff --git a/src/main.cpp b/src/main.cpp index 59a4e837..90cf9e0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -400,6 +400,27 @@ int main(int argc, char *argv[]) settings.setValue("gamePath", gamePath.toUtf8().constData()); } + int edition = 0; + if (settings.contains("game_edition")) { + edition = settings.value("game_edition").toInt(); + } else { + std::vector editions = GameInfo::instance().getSteamVariants(); + if (editions.size() < 2) { + edition = 0; + } else { + SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL); + int index = 0; + for (auto iter = editions.begin(); iter != editions.end(); ++iter) { + selection.addChoice(ToQString(*iter), "", index++); + } + if (selection.exec() == QDialog::Rejected) { + return -1; + } else { + settings.setValue("game_edition", selection.getChoiceData().toInt()); + } + } + } + qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); ExecutablesList executablesList; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 14bb3b59..cf8e355f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2134,7 +2134,7 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList } } catch (const std::runtime_error&) { qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); - return INVALID_HANDLE_VALUE; + binary = QFileInfo(executable); } } diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 8d672675..faf8c95b 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -560,7 +560,6 @@ void ModInfoRegular::addNexusCategory(int categoryID) m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); } - void ModInfoRegular::setIsEndorsed(bool endorsed) { if (m_EndorsedState != ENDORSED_NEVER) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 18bbfd5a..0fdcf6fa 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -169,10 +169,16 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const int category = modInfo->getPrimaryCategory(); if (category != -1) { CategoryFactory &categoryFactory = CategoryFactory::instance(); - try { - return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category)); - } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); + if (categoryFactory.categoryExists(category)) { + try { + return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category)); + } catch (const std::exception &e) { + qCritical("failed to retrieve category name: %s", e.what()); + return QString(); + } + } else { + qWarning("category %d doesn't exist (may have been removed)", category); + modInfo->setCategory(category, false); return QString(); } } else { diff --git a/src/settings.cpp b/src/settings.cpp index 4f301b0c..36a4f1f8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -159,7 +159,7 @@ bool Settings::automaticLoginEnabled() const QString Settings::getSteamAppID() const { - return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId())).toString(); + return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); } QString Settings::getDownloadDirectory() const diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index 79dd90c9..c673fa1b 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -133,10 +133,17 @@ std::wstring Fallout3Info::getOMODExt() return L"fomod"; } +std::vector Fallout3Info::getSteamVariants() const +{ + return boost::assign::list_of(L"Regular")(L"Game Of The Year"); +} -std::wstring Fallout3Info::getSteamAPPId() +std::wstring Fallout3Info::getSteamAPPId(int variant) const { - return L"22300"; + switch (variant) { + case 1: return L"22370"; + default: return L"22300"; + } } diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index 95a76471..0fa97d41 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -66,7 +66,8 @@ public: virtual std::wstring getReferenceDataFile(); virtual std::wstring getOMODExt(); - virtual std::wstring getSteamAPPId(); + virtual std::vector getSteamVariants() const; + virtual std::wstring getSteamAPPId(int variant = 0) const; virtual std::wstring getSEName(); diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 6da454ee..7d7f0098 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -136,7 +136,7 @@ std::wstring FalloutNVInfo::getOMODExt() } -std::wstring FalloutNVInfo::getSteamAPPId() +std::wstring FalloutNVInfo::getSteamAPPId(int) const { return L"22380"; } diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index 2f6cba7b..3960c951 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -68,7 +68,7 @@ public: virtual std::wstring getReferenceDataFile(); virtual std::wstring getOMODExt(); - virtual std::wstring getSteamAPPId(); + virtual std::wstring getSteamAPPId(int variant = 0) const; virtual std::wstring getSEName(); diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index f74e52b4..00bb42fd 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -30,6 +30,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include namespace MOShared { @@ -184,6 +185,11 @@ bool GameInfo::requiresSteam() const return FileExists(getGameDirectory().append(L"\\steam_api.dll")); } +std::vector GameInfo::getSteamVariants() const +{ + return boost::assign::list_of(L"Regular"); +} + std::wstring GameInfo::getLocalAppFolder() const { diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 14f52f05..0221dd1b 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -137,7 +137,8 @@ public: virtual std::wstring getOMODExt() = 0; - virtual std::wstring getSteamAPPId() = 0; + virtual std::vector getSteamVariants() const; + virtual std::wstring getSteamAPPId(int variant = 0) const = 0; virtual std::wstring getSEName() = 0; diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index 7c6d5cbb..b3e65e59 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -243,7 +243,7 @@ std::wstring OblivionInfo::getOMODExt() } -std::wstring OblivionInfo::getSteamAPPId() +std::wstring OblivionInfo::getSteamAPPId(int) const { return L"22330"; } diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index 13374aa3..6a9f56ca 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -64,7 +64,7 @@ public: virtual std::wstring getReferenceDataFile(); virtual std::wstring getOMODExt(); - virtual std::wstring getSteamAPPId(); + virtual std::wstring getSteamAPPId(int variant = 0) const; virtual std::wstring getSEName(); diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 71d791f5..0a0dd98d 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -169,7 +169,7 @@ std::wstring SkyrimInfo::getOMODExt() } -std::wstring SkyrimInfo::getSteamAPPId() +std::wstring SkyrimInfo::getSteamAPPId(int) const { return L"72850"; } diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index c5d31ff0..ae5ab81f 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -71,7 +71,7 @@ public: virtual std::wstring getReferenceDataFile(); virtual std::wstring getOMODExt(); - virtual std::wstring getSteamAPPId(); + virtual std::wstring getSteamAPPId(int variant = 0) const; virtual std::wstring getSEName(); diff --git a/src/version.rc b/src/version.rc index e71bc1b5..55de3798 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 1,0,5,0 -#define VER_FILEVERSION_STR "1,0,5,0\0" +#define VER_FILEVERSION 1,0,7,0 +#define VER_FILEVERSION_STR "1,0,7,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 469cc2d945afebb1291a825339642b5e95f0e223 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 7 Dec 2013 12:48:18 +0100 Subject: - download manager now saves the file times on nexus, for potential later use in version check - nexus interface now supports 301 redirects - now using the new nexus url format - bugfix: "visit on nexus" used an outdated url scheme and thus caused unnecessary redirection --- src/downloadmanager.cpp | 26 ++++++++++++++++--- src/downloadmanager.h | 5 ++++ src/mainwindow.cpp | 2 +- src/nexusinterface.cpp | 59 ++++++++++++++++++++++++++------------------ src/nexusinterface.h | 7 +++--- src/shared/fallout3info.cpp | 4 +-- src/shared/fallout3info.h | 1 + src/shared/falloutnvinfo.cpp | 4 +-- src/shared/falloutnvinfo.h | 1 + src/shared/gameinfo.h | 1 + src/shared/oblivioninfo.cpp | 4 +-- src/shared/oblivioninfo.h | 1 + src/shared/skyriminfo.cpp | 4 +-- src/shared/skyriminfo.h | 1 + 14 files changed, 80 insertions(+), 40 deletions(-) (limited to 'src/shared/gameinfo.h') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 779b052c..48484d24 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -153,7 +153,8 @@ QString DownloadManager::DownloadInfo::currentURL() DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false) + : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + m_DateExpression("/Date\\((\\d+)\\)/") { connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); } @@ -851,7 +852,6 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) setState(info, STATE_PAUSED); } else { if (bytesTotal > info->m_TotalSize) { - qDebug("file size %s: %lld", qPrintable(info->m_FileName), bytesTotal); info->m_TotalSize = bytesTotal; } int oldProgress = info->m_Progress; @@ -883,6 +883,7 @@ void DownloadManager::createMetaFile(DownloadInfo *info) metaFile.setValue("name", info->m_NexusInfo.m_Name); metaFile.setValue("modName", info->m_NexusInfo.m_ModName); metaFile.setValue("version", info->m_NexusInfo.m_Version); + metaFile.setValue("fileTime", info->m_NexusInfo.m_FileTime); metaFile.setValue("fileCategory", info->m_NexusInfo.m_FileCategory); metaFile.setValue("newestVersion", info->m_NexusInfo.m_NewestVersion); metaFile.setValue("category", info->m_NexusInfo.m_Category); @@ -914,11 +915,9 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r DownloadInfo *info = downloadInfoByID(userData.toInt()); if (info == NULL) return; - info->m_NexusInfo.m_Category = result["category_id"].toInt(); info->m_NexusInfo.m_ModName = result["name"].toString().trimmed(); info->m_NexusInfo.m_NewestVersion = result["version"].toString(); - if (info->m_FileID != 0) { setState(info, STATE_READY); } else { @@ -927,6 +926,17 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r } +QDateTime DownloadManager::matchDate(const QString &timeString) +{ + if (m_DateExpression.exactMatch(timeString)) { + return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong()); + } else { + qWarning("date not matched: %s", qPrintable(timeString)); + return QDateTime::currentDateTime(); + } +} + + void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); @@ -960,7 +970,11 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD (fileNameVariant == info->m_FileName) || (fileNameVariant == alternativeLocalName)) { info->m_NexusInfo.m_Name = fileInfo["name"].toString(); info->m_NexusInfo.m_Version = fileInfo["version"].toString(); + if (info->m_NexusInfo.m_Version.isEmpty()) { + info->m_NexusInfo.m_Version = info->m_NexusInfo.m_NewestVersion; + } info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt(); + info->m_NexusInfo.m_FileTime = matchDate(fileInfo["date"].toString()); info->m_FileID = fileInfo["id"].toInt(); found = true; break; @@ -1014,7 +1028,11 @@ void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userD info.m_Name = result["name"].toString(); info.m_Version = result["version"].toString(); + if (info.m_Version.isEmpty()) { + info.m_Version = info.m_NewestVersion; + } info.m_FileName = result["uri"].toString(); + info.m_FileTime = matchDate(result["date"].toString()); if (userData.isValid()) { m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(info), userData.toString())); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index e82ea064..099e6084 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -46,6 +46,7 @@ struct NexusInfo { QString m_NewestVersion; QString m_FileName; QVariantList m_DownloadMap; + QDateTime m_FileTime; bool m_Set; }; Q_DECLARE_METATYPE(NexusInfo) @@ -437,6 +438,8 @@ private: DownloadInfo *downloadInfoByID(unsigned int id); + QDateTime matchDate(const QString &timeString); + private: static const int AUTOMATIC_RETRIES = 3; @@ -458,6 +461,8 @@ private: bool m_ShowHidden; + QRegExp m_DateExpression; + }; #endif // DOWNLOADMANAGER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5330017..cca10279 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3214,7 +3214,7 @@ void MainWindow::visitOnNexus_clicked() { int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); if (modID > 0) { - nexusLinkActivated(QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage())).arg(modID)); + nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage())).arg(modID)); } else { MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 6a4ae046..6b5cf19c 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -380,29 +380,33 @@ void NexusInterface::nextRequest() info.m_Timeout->setInterval(60000); QString url; - switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILES: { - url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - QString modIDList = VectorJoin(info.m_ModIDList, ","); - modIDList = "[" + modIDList + "]"; - url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); - } break; + if (!info.m_Reroute) { + switch (info.m_Type) { + case NXMRequestInfo::TYPE_DESCRIPTION: { + url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILES: { + url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + QString modIDList = VectorJoin(info.m_ModIDList, ","); + modIDList = "[" + modIDList + "]"; + url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); + } break; + } + url.append(QString("?game_id=%1").arg(GameInfo::instance().getNexusGameID())); + } else { + url = info.m_URL; } - QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); request.setRawHeader("User-Agent", @@ -433,13 +437,21 @@ void NexusInterface::requestFinished(std::list::iterator iter) qWarning("request failed: %s", reply->errorString().toUtf8().constData()); emit nxmRequestFailed(iter->m_ModID, iter->m_UserData, iter->m_ID, reply->errorString()); } else { + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode == 301) { + // redirect request, return request to queue + iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); + iter->m_Reroute = true; + m_RequestQueue.enqueue(*iter); + nextRequest(); + return; + } QByteArray data = reply->readAll(); if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { QString nexusError(reply->rawHeader("NexusErrorInfo")); if (nexusError.length() == 0) { nexusError = tr("empty response"); } - emit nxmRequestFailed(iter->m_ModID, iter->m_UserData, iter->m_ID, nexusError); } else { bool ok; @@ -509,7 +521,6 @@ void NexusInterface::requestTimeout() qWarning("invalid sender type"); return; } - qWarning("request timeout"); for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { if (iter->m_Timeout == timer) { // this abort causes a "request failed" which cleans up the rest diff --git a/src/nexusinterface.h b/src/nexusinterface.h index da5fe02a..e7a01b0f 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -270,18 +270,19 @@ private: QVariant m_UserData; QTimer *m_Timeout; QString m_URL; + bool m_Reroute; 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) {} + m_Timeout(NULL), m_Reroute(false), 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) {} + m_Timeout(NULL), m_Reroute(false), 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) {} + m_Timeout(NULL), m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(url) {} private: static QAtomicInt s_NextID; diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index c673fa1b..864a67be 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -155,13 +155,13 @@ std::wstring Fallout3Info::getSEName() std::wstring Fallout3Info::getNexusPage() { - return L"http://fallout3.nexusmods.com"; + return L"http://www.nexusmods.com/fallout3"; } std::wstring Fallout3Info::getNexusInfoUrlStatic() { - return L"http://fallout3.nexusmods.com"; + return L"http://www.nexusmods.com/fallout3"; } diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index 0fa97d41..4bcb9d37 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -76,6 +76,7 @@ public: virtual std::wstring getNexusInfoUrl() { return Fallout3Info::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); virtual int getNexusModID() { return Fallout3Info::getNexusModIDStatic(); } + virtual int getNexusGameID() { return 120; } virtual void createProfile(const std::wstring &directory, bool useDefaults); virtual void repairProfile(const std::wstring &directory); diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 7d7f0098..8df9607d 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -209,13 +209,13 @@ std::wstring FalloutNVInfo::getSEName() std::wstring FalloutNVInfo::getNexusPage() { - return L"http://newvegas.nexusmods.com"; + return L"http://www.nexusmods.com/newvegas"; } std::wstring FalloutNVInfo::getNexusInfoUrlStatic() { - return L"http://newvegas.nexusmods.com"; + return L"http://www.nexusmods.com/newvegas"; } diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index 3960c951..e25d2e02 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -77,6 +77,7 @@ public: virtual std::wstring getNexusInfoUrl() { return FalloutNVInfo::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); virtual int getNexusModID() { return FalloutNVInfo::getNexusModIDStatic(); } + virtual int getNexusGameID() { return 130; } virtual void createProfile(const std::wstring &directory, bool useDefaults); virtual void repairProfile(const std::wstring &directory); diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 0221dd1b..d517fc1b 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -145,6 +145,7 @@ public: virtual std::wstring getNexusPage() = 0; virtual std::wstring getNexusInfoUrl() = 0; virtual int getNexusModID() = 0; + virtual int getNexusGameID() = 0; // clone relevant files to the specified directory virtual void createProfile(const std::wstring &directory, bool useDefaults) = 0; diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index b3e65e59..1438de0a 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -191,13 +191,13 @@ std::wstring OblivionInfo::getSEName() std::wstring OblivionInfo::getNexusPage() { - return L"http://oblivion.nexusmods.com"; + return L"http://www.nexusmods.com/oblivion"; } std::wstring OblivionInfo::getNexusInfoUrlStatic() { - return L"http://oblivion.nexusmods.com"; + return L"http://www.nexusmods.com/oblivion"; } diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index 6a9f56ca..dfa53575 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -73,6 +73,7 @@ public: virtual std::wstring getNexusInfoUrl() { return OblivionInfo::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); virtual int getNexusModID() { return OblivionInfo::getNexusModIDStatic(); } + virtual int getNexusGameID() { return 101; } virtual void createProfile(const std::wstring &directory, bool useDefaults); virtual void repairProfile(const std::wstring &directory); diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 0a0dd98d..a8b9a433 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -183,13 +183,13 @@ std::wstring SkyrimInfo::getSEName() std::wstring SkyrimInfo::getNexusPage() { - return L"http://skyrim.nexusmods.com"; + return L"http://www.nexusmods.com/skyrim"; } std::wstring SkyrimInfo::getNexusInfoUrlStatic() { - return L"http://skyrim.nexusmods.com"; + return L"http://www.nexusmods.com/skyrim"; } diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index ae5ab81f..7da523a1 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -81,6 +81,7 @@ public: virtual std::wstring getNexusInfoUrl() { return SkyrimInfo::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); virtual int getNexusModID() { return SkyrimInfo::getNexusModIDStatic(); } + virtual int getNexusGameID() { return 110; } virtual void createProfile(const std::wstring &directory, bool useDefaults); virtual void repairProfile(const std::wstring &directory); -- cgit v1.3.1 From 9ee90a35dc3ebdbc76b18cbeb72995345ee37052 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 8 Dec 2013 14:16:25 +0100 Subject: - MO now applies a minimum to the nmm-compatibility field. - bugfix: "visit on nexus" directed the browser to the servers meant for nmm - bugfix: url for "check all for updates" and "enorse/unendorse" were not constructed correctly --- src/mainwindow.cpp | 2 +- src/nexusinterface.cpp | 5 ++++- src/settings.cpp | 13 +++++++++---- src/shared/fallout3info.cpp | 8 ++++++-- src/shared/fallout3info.h | 2 +- src/shared/falloutnvinfo.cpp | 8 ++++++-- src/shared/falloutnvinfo.h | 2 +- src/shared/gameinfo.h | 2 +- src/shared/oblivioninfo.cpp | 8 ++++++-- src/shared/oblivioninfo.h | 2 +- src/shared/skyriminfo.cpp | 8 ++++++-- src/shared/skyriminfo.h | 2 +- src/version.rc | 4 ++-- 13 files changed, 45 insertions(+), 21 deletions(-) (limited to 'src/shared/gameinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b435cda0..17f2de09 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3215,7 +3215,7 @@ void MainWindow::visitOnNexus_clicked() { int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); if (modID > 0) { - nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage())).arg(modID)); + nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID)); } else { MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 6b5cf19c..309915aa 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -381,6 +381,7 @@ void NexusInterface::nextRequest() QString url; if (!info.m_Reroute) { + bool hasParams = false; switch (info.m_Type) { case NXMRequestInfo::TYPE_DESCRIPTION: { url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); @@ -396,14 +397,16 @@ void NexusInterface::nextRequest() } break; case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); + hasParams = true; } break; case NXMRequestInfo::TYPE_GETUPDATES: { QString modIDList = VectorJoin(info.m_ModIDList, ","); modIDList = "[" + modIDList + "]"; url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); + hasParams = true; } break; } - url.append(QString("?game_id=%1").arg(GameInfo::instance().getNexusGameID())); + url.append(QString("%1game_id=%2").arg(hasParams ? '&' : '?').arg(GameInfo::instance().getNexusGameID())); } else { url = info.m_URL; } diff --git a/src/settings.cpp b/src/settings.cpp index 144049aa..8086672a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -214,7 +214,12 @@ QString Settings::getModDirectory() const QString Settings::getNMMVersion() const { - return m_Settings.value("Settings/nmm_version", "0.34.0").toString(); + static const QString MIN_NMM_VERSION = "0.46.0"; + QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); + if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { + result = MIN_NMM_VERSION; + } + return result; } bool Settings::getNexusLogin(QString &username, QString &password) const @@ -583,9 +588,9 @@ void Settings::query(QWidget *parent) downloadDirEdit->setText(getDownloadDirectory()); modDirEdit->setText(getModDirectory()); cacheDirEdit->setText(getCacheDirectory()); - offlineBox->setChecked(m_Settings.value("Settings/offline_mode", false).toBool()); - proxyBox->setChecked(m_Settings.value("Settings/use_proxy", false).toBool()); - nmmVersionEdit->setText(m_Settings.value("Settings/nmm_version", "0.33.1").toString()); + offlineBox->setChecked(offlineMode()); + proxyBox->setChecked(useProxy()); + nmmVersionEdit->setText(getNMMVersion()); logLevelBox->setCurrentIndex(logLevel()); // display plugin settings diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index cc90d6fa..1808d1d9 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -153,9 +153,13 @@ std::wstring Fallout3Info::getSEName() } -std::wstring Fallout3Info::getNexusPage() +std::wstring Fallout3Info::getNexusPage(bool nmmScheme) { - return L"http://nmm.nexusmods.com/fallout3"; + if (nmmScheme) { + return L"http://nmm.nexusmods.com/fallout3"; + } else { + return L"http://www.nexusmods.com/fallout3"; + } } diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index 4bcb9d37..9575103b 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -71,7 +71,7 @@ public: virtual std::wstring getSEName(); - virtual std::wstring getNexusPage(); + virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); virtual std::wstring getNexusInfoUrl() { return Fallout3Info::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index abff9323..e7e747e2 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -207,9 +207,13 @@ std::wstring FalloutNVInfo::getSEName() } -std::wstring FalloutNVInfo::getNexusPage() +std::wstring FalloutNVInfo::getNexusPage(bool nmmScheme) { - return L"http://nmm.nexusmods.com/newvegas"; + if (nmmScheme) { + return L"http://nmm.nexusmods.com/newvegas"; + } else { + return L"http://www.nexusmods.com/newvegas"; + } } diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index e25d2e02..8767fdb0 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -72,7 +72,7 @@ public: virtual std::wstring getSEName(); - virtual std::wstring getNexusPage(); + virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); virtual std::wstring getNexusInfoUrl() { return FalloutNVInfo::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index d517fc1b..10775e6c 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -142,7 +142,7 @@ public: virtual std::wstring getSEName() = 0; - virtual std::wstring getNexusPage() = 0; + virtual std::wstring getNexusPage(bool nmmScheme = true) = 0; virtual std::wstring getNexusInfoUrl() = 0; virtual int getNexusModID() = 0; virtual int getNexusGameID() = 0; diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index c84f8b88..d8daa0f7 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -189,9 +189,13 @@ std::wstring OblivionInfo::getSEName() } -std::wstring OblivionInfo::getNexusPage() +std::wstring OblivionInfo::getNexusPage(bool nmmScheme) { - return L"http://nmm.nexusmods.com/oblivion"; + if (nmmScheme) { + return L"http://nmm.nexusmods.com/oblivion"; + } else { + return L"http://www.nexusmods.com/oblivion"; + } } diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index dfa53575..ba27aa40 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -68,7 +68,7 @@ public: virtual std::wstring getSEName(); - virtual std::wstring getNexusPage(); + virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); virtual std::wstring getNexusInfoUrl() { return OblivionInfo::getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 38b2cf37..1620bcc3 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -181,9 +181,13 @@ std::wstring SkyrimInfo::getSEName() } -std::wstring SkyrimInfo::getNexusPage() +std::wstring SkyrimInfo::getNexusPage(bool nmmScheme) { - return L"http://nmm.nexusmods.com/skyrim"; + if (nmmScheme) { + return L"http://nmm.nexusmods.com/skyrim"; + } else { + return L"http://www.nexusmods.com/skyrim"; + } } diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index 7da523a1..438beb41 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -75,7 +75,7 @@ public: virtual std::wstring getSEName(); - virtual std::wstring getNexusPage(); + virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); virtual std::wstring getNexusInfoUrl() { return SkyrimInfo::getNexusInfoUrlStatic(); } diff --git a/src/version.rc b/src/version.rc index b91dce85..811ee7ca 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 1,0,10,0 -#define VER_FILEVERSION_STR "1,0,10,0\0" +#define VER_FILEVERSION 1,0,11,0 +#define VER_FILEVERSION_STR "1,0,11,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From c017f4a0d50b67a44e276bd5ae8929ed3990c62c Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 5 Apr 2014 15:14:37 +0200 Subject: - added buttons to backup and restore the modlist and pluginlist - replaced boss integration with loot --- src/ModOrganizer.pro | 3 +- src/main.cpp | 23 +--- src/mainwindow.cpp | 204 ++++++++++++++++++++++++++++++++- src/mainwindow.h | 16 ++- src/mainwindow.ui | 112 +++++++++++++++++-- src/modlist.cpp | 2 +- src/pluginlist.cpp | 224 +------------------------------------ src/pluginlist.h | 83 +------------- src/profile.h | 6 +- src/resources.qrc | 3 + src/resources/arrange-boxes.png | Bin 0 -> 1634 bytes src/resources/document-save_32.png | Bin 0 -> 1971 bytes src/resources/edit-undo.png | Bin 0 -> 1601 bytes src/selectiondialog.cpp | 123 ++++++++++---------- src/selectiondialog.h | 96 ++++++++-------- src/shared/gameinfo.cpp | 8 ++ src/shared/gameinfo.h | 1 + src/spawn.cpp | 29 ++++- src/spawn.h | 8 +- 19 files changed, 486 insertions(+), 455 deletions(-) create mode 100644 src/resources/arrange-boxes.png create mode 100644 src/resources/document-save_32.png create mode 100644 src/resources/edit-undo.png (limited to 'src/shared/gameinfo.h') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 05b05855..62de3f66 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -14,7 +14,8 @@ SUBDIRS = bsatk \ BossDummy \ pythonRunner \ boss_modified \ - esptk + esptk \ + loot_cli plugins.depends = pythonRunner hookdll.depends = shared diff --git a/src/main.cpp b/src/main.cpp index ac903615..d1c5e263 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -80,24 +80,6 @@ using namespace MOBase; using namespace MOShared; -void removeOldLogfiles() -{ - QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"), - QDir::Files, QDir::Name); - - if (files.count() > 5) { - QStringList deleteFiles; - for (int i = 0; i < files.count() - 5; ++i) { - deleteFiles.append(files.at(i).absoluteFilePath()); - } - - if (!shellDelete(deleteFiles)) { - qWarning("failed to remove log files: %s", qPrintable(windowsErrorString(::GetLastError()))); - } - } -} - - // set up required folders (for a first install or after an update or to fix a broken installation) bool bootstrap() { @@ -111,7 +93,7 @@ bool bootstrap() } // cycle logfile - removeOldLogfiles(); + removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name); // create organizer directories QString dirNames[] = { @@ -120,8 +102,7 @@ bool bootstrap() QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss") + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) }; static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bf5ea7f0..87360f3c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -109,8 +109,10 @@ along with Mod Organizer. If not, see . #include #endif #include +#include #include #include +#include #ifdef TEST_MODELS @@ -2649,7 +2651,6 @@ void MainWindow::directory_refreshed() delete oldStructure; refreshDataTree(); - refreshLists(); } else { // TODO: don't know why this happens, this slot seems to get called twice with only one emit return; @@ -5155,20 +5156,213 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } + +void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = NULL; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + qCritical("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + qCritical("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string MainWindow::readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + void MainWindow::on_bossButton_clicked() { try { this->setEnabled(false); ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); - LockedDialog dialog(this, tr("BOSS working"), false); + QProgressDialog dialog(this); + dialog.setLabelText(tr("LOOT working")); + dialog.setMaximum(0); dialog.show(); - qApp->processEvents(); - m_PluginList.bossSort(); - savePluginList(); + QStringList parameters; + parameters << "--game" << ToQString(GameInfo::instance().getGameName()) + << "--gamePath" << ToQString(GameInfo::instance().getGameDirectory()); + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/lootcli.exe"), + parameters.join(" "), + m_CurrentProfile->getName(), + m_Settings.logLevel(), + qApp->applicationDirPath(), + true, + stdOutWrite); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + if (loot != INVALID_HANDLE_VALUE) { + while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) { + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + if (dialog.wasCanceled()) { + ::TerminateProcess(loot, 1); + } + std::string lootOut = readFromPipe(stdOutRead); + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + foreach (const std::string &line, lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t reportidx = line.find("[report]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (reportidx != std::string::npos) { + qDebug("report at %s", line.substr(reportidx + 9).c_str()); + } else { + qDebug("%s", line.c_str()); + } + } + } + } + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + qDebug("%s", remainder.c_str()); + } + + refreshESPList(); + + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + } + dialog.hide(); } catch (const std::exception &e) { reportError(tr("failed to run boss: %1").arg(e.what())); ui->bossButton->setEnabled(false); } } + + +const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; +const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; +const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; + + +bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +{ + QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); + if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { + QFileInfo fileInfo(filePath); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + return true; + } else { + return false; + } +} + +void MainWindow::on_saveButton_clicked() +{ + savePluginList(); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getPluginsFileName(), now) + && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) + && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { + MessageDialog::showMessage(tr("Backup of load order created"), this); + } +} + +QString MainWindow::queryRestore(const QString &filePath) +{ + QFileInfo pluginFileInfo(filePath); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + + SelectionDialog dialog(tr("Choose backup to restore"), this); + QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); + QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); + foreach(const QFileInfo &info, files) { + if (exp.exactMatch(info.fileName())) { + QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); + dialog.addChoice(time.toString(), "", exp.cap(1)); + } else if (exp2.exactMatch(info.fileName())) { + dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); + } + } + + if (dialog.numChoices() == 0) { + QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + return QString(); + } + + if (dialog.exec() == QDialog::Accepted) { + return dialog.getChoiceData().toString(); + } else { + return QString(); + } +} + +void MainWindow::on_restoreButton_clicked() +{ + QString pluginName = m_CurrentProfile->getPluginsFileName(); + QString choice = queryRestore(pluginName); + if (!choice.isEmpty()) { + QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); + QString lockedName = m_CurrentProfile->getLockedOrderFileName(); + if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || + !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshESPList(); + } +} + +void MainWindow::on_saveModsButton_clicked() +{ + m_CurrentProfile->writeModlistNow(true); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { + MessageDialog::showMessage(tr("Backup of modlist created"), this); + } +} +void MainWindow::on_restoreModsButton_clicked() +{ + QString modlistName = m_CurrentProfile->getModlistFileName(); + QString choice = queryRestore(modlistName); + if (!choice.isEmpty()) { + if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshModList(false); + } +} + diff --git a/src/mainwindow.h b/src/mainwindow.h index 23923677..c5b4a8d3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -154,6 +154,8 @@ public: void saveArchiveList(); + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); public slots: void displayColumnSelection(const QPoint &pos); @@ -207,8 +209,6 @@ private: bool nexusLogin(); - void saveCurrentESPList(); - bool testForSteam(); void startSteam(); @@ -290,11 +290,18 @@ private: void activateProxy(bool activate); void installTranslator(const QString &name); + bool createBackup(const QString &filePath, const QDateTime &time); + QString queryRestore(const QString &filePath); + private: static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; + static const char *PATTERN_BACKUP_GLOB; + static const char *PATTERN_BACKUP_REGEX; + static const char *PATTERN_BACKUP_DATE; + private: Ui::MainWindow *ui; @@ -574,6 +581,11 @@ private slots: // ui slots void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); void on_bossButton_clicked(); + + void on_saveButton_clicked(); + void on_restoreButton_clicked(); + void on_restoreModsButton_clicked(); + void on_saveModsButton_clicked(); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 26cbbf83..dc91121c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -115,7 +115,7 @@ 2 - + @@ -170,6 +170,47 @@ p, li { white-space: pre-wrap; } + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + @@ -617,6 +658,68 @@ p, li { white-space: pre-wrap; } 0 + + + + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + @@ -725,13 +828,6 @@ p, li { white-space: pre-wrap; } - - - - Sort - - - diff --git a/src/modlist.cpp b/src/modlist.cpp index e2cb7cf0..836406e4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -669,7 +669,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa } if (source.count() != 0) { - shellMove(source, target, NULL); + shellMove(source, target); } return true; diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index af6f42da..1f4ed9b1 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -78,7 +78,6 @@ PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent) , m_FontMetrics(QFont()) , m_SaveTimer(this) - , m_BOSS(NULL) { m_SaveTimer.setSingleShot(true); connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); @@ -95,11 +94,6 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { - if (m_BOSS != NULL) { - m_BOSS->CleanUpAPI(); - delete m_BOSS; - m_BOSS = NULL; - } } @@ -595,225 +589,9 @@ void PluginList::refreshLoadOrder() } -class boss_exception : public std::runtime_error { -public: - boss_exception(const std::string &message) : std::runtime_error(message) {} -}; - -#define THROW_BOSS_ERROR(obj) \ - uint8_t *message; \ - obj->GetLastErrorDetails(&message); \ - throw boss_exception(std::string(reinterpret_cast(message))); - -#define U8(text) reinterpret_cast(text) - - -void outputBossLog(const QString &filename) -{ - QFile file(filename); - if (file.open(QIODevice::ReadOnly)) { - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - if (line.length() < 1) - continue; - - int endFirstWord = line.indexOf(':'); - if (endFirstWord < 0) { - endFirstWord = 0; - } - QString firstWord = line.mid(0, endFirstWord); - if (firstWord == "DEBUG") { - qDebug("(boss) %s", line.mid(endFirstWord + 2).constData()); - } else { - qWarning("(boss) %s", line.mid(endFirstWord + 2).constData()); - } - } - } - file.resize(0); -} - - -boss_db PluginList::initBoss() -{ - boss_db result; - bool firstRun = (m_BOSS == nullptr); - if (firstRun) { - m_BOSS = new BossDLL(TEXT("dlls\\boss.dll")); - - if (!m_BOSS->IsCompatibleVersion(2,1,1)) { - throw MyException(tr("BOSS dll incompatible")); - } - uint8_t *versionString; - if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - qDebug("using boss version %s", versionString); - - m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name - m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4); - } - - if (m_BOSS->CreateBossDb(&result, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) { - uint8_t *message; - m_BOSS->GetLastErrorDetails(&message); - std::string messageCopy(reinterpret_cast(message)); - delete m_BOSS; - m_BOSS = NULL; - throw boss_exception(messageCopy); - } - qApp->processEvents(); - - QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt"); - - if (firstRun) { - uint32_t res = m_BOSS->UpdateMasterlist(result, U8(masterlistName.toUtf8().constData())); - qApp->processEvents(); - if (res == BossDLL::RESULT_OK) { - qDebug("boss masterlist updated"); - } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) { - qDebug("boss masterlist already up-to-date"); - } else { - THROW_BOSS_ERROR(m_BOSS) - } - } - if (m_BOSS->Load(result, - U8(masterlistName.toUtf8().constData()), - U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - return result; -} - -void PluginList::convertPluginListForBoss(boss_db db, boost::ptr_vector &inputPlugins, std::vector &activePlugins) -{ - foreach (int idx, m_ESPsByPriority) { - QString fileName = m_ESPs[idx].m_Name; - QByteArray name = fileName.toUtf8(); - - uint8_t *nameU8 = new uint8_t[name.length() + 1]; - memcpy(nameU8, name.constData(), name.length() + 1); - if (m_ESPs[idx].m_Enabled) { - activePlugins.push_back(nameU8); - } - inputPlugins.push_back(nameU8); - } - if (m_BOSS->SetActivePluginsDumb(db, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } -} - -void PluginList::applyBOSSSorting(boss_db db, std::map &lockedLoadOrder, uint8_t **pluginList, size_t size, - int &priority, int &loadOrder, bool recognized, const char *extension) -{ - for (size_t i = 0; i < size; ++i) { - QString name = QString::fromUtf8(reinterpret_cast(pluginList[i])).toLower(); - if (name.endsWith(extension)) { - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - // boss seems to report plugins from userlist as sorted that aren't even installed - continue; - } - - BossMessage *message; - size_t numMessages = 0; - m_BOSS->GetPluginMessages(db, pluginList[i], &message, &numMessages); - BossInfo newInfo; - for (size_t im = 0; im < numMessages; ++im) { - newInfo.m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast(message[im].message))); - } - newInfo.m_BOSSUnrecognized = !recognized; - m_BossInfo[name] = newInfo; - - // locked order plugins are not inserted by boss sorting ... - if (m_LockedOrder.find(name) != m_LockedOrder.end()) { - continue; - } - - // ... but by their enforced priority - while (lockedLoadOrder.find(loadOrder) != lockedLoadOrder.end()) { - auto lloIter = lockedLoadOrder.find(loadOrder); - auto nameIter = m_ESPsByName.find(lloIter->second); - if (nameIter != m_ESPsByName.end()) { - m_ESPs[nameIter->second].m_Priority = priority++; - if (m_ESPs[nameIter->second].m_Enabled) { - m_ESPs[nameIter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[nameIter->second].m_LoadOrder = -1; - } - } - lockedLoadOrder.erase(lloIter); - } - - m_ESPs[iter->second].m_Priority = priority++; - if (m_ESPs[iter->second].m_Enabled) { - m_ESPs[iter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[iter->second].m_LoadOrder = -1; - } - } - } -} - -void PluginList::bossSort() +void PluginList::lootSort() { - boss_db db = initBoss(); - ON_BLOCK_EXIT([&] { m_BOSS->DestroyBossDb(db); }); - - // create a boss-compatible representation of our current mod list. - boost::ptr_vector inputPlugins; - std::vector activePlugins; - convertPluginListForBoss(db, inputPlugins, activePlugins); - - // sort mods in-memory - uint8_t **sortedPlugins; - uint8_t **unrecognizedPlugins; - size_t sizeSorted, sizeUnrecognized; - if (m_BOSS->SortCustomMods(db, - inputPlugins.c_array(), inputPlugins.size(), - &sortedPlugins, &sizeSorted, - &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) { - THROW_BOSS_ERROR(m_BOSS) - } - - // output the log from boss to our own log to make it visible - outputBossLog(m_TempFile.fileName()); - - qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized); - ChangeBracket layoutChange(this); - - std::map lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair &ele) { lockedLoadOrder[ele.second] = ele.first; }); - - int priority = 0; - int loadOrder = 0; - applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esm"); - applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esm"); - applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esp"); - applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esp"); - - // applyBOSSSorting removed entries from lockedLoadOrder when they were inserted so everything that's left is plugins - // locked to the end of the list. Now this inserts the rest in the ascending priority order. If the list is too short - // to place the plugins in their intended position then this guarantees plugins are kept in the correct relative order - // but it doesn't minimize the number of misplaced plugins. - for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { - auto nameIter = m_ESPsByName.find(iter->second); - if (nameIter != m_ESPsByName.end()) { - m_ESPs[nameIter->second].m_Priority = priority++; - if (m_ESPs[nameIter->second].m_Enabled) { - m_ESPs[nameIter->second].m_LoadOrder = loadOrder++; - } else { - m_ESPs[nameIter->second].m_LoadOrder = -1; - } - } - } - - // inform view of the changed data - updateIndices(); - layoutChange.finish(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); - m_Refreshed(); } IPluginList::PluginState PluginList::state(const QString &name) const diff --git a/src/pluginlist.h b/src/pluginlist.h index fff2a595..5e8557e2 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -181,7 +181,7 @@ public: void refreshLoadOrder(); - void bossSort(); + void lootSort(); public: virtual PluginState state(const QString &name) const; @@ -204,7 +204,6 @@ public: // implementation of the QAbstractTableModel interface virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; virtual QModelIndex parent(const QModelIndex &child) const; - void applyBOSSSorting(boss_db db, std::map &lockedLoadOrder, uint8_t **pluginList, size_t size, int &priority, int &loadOrder, bool recognized, const char *extension); public slots: /** @@ -260,82 +259,6 @@ private: friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); - class BossDLL : public PDLL { - DECLARE_CLASS(BossDLL) - - DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*) - DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db) - DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI) - - DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*) - DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t) - - DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*) - - DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*) - DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*) - - DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t) - DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**) - - DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**) - DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t) - - DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*) - - enum ResultCode { - RESULT_OK = 0, - RESULT_NO_MASTER_FILE = 1, - RESULT_FILE_READ_FAIL = 2, - RESULT_FILE_WRITE_FAIL = 3, - RESULT_FILE_NOT_UTF8 = 4, - RESULT_FILE_NOT_FOUND = 5, - RESULT_FILE_PARSE_FAIL = 6, - RESULT_CONDITION_EVAL_FAIL = 7, - RESULT_REGEX_EVAL_FAIL = 8, - RESULT_NO_GAME_DETECTED = 9, - RESULT_ENCODING_CONVERSION_FAIL = 10, - RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11, - RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12, - RESULT_READ_UPDATE_FILE_LIST_FAIL = 13, - RESULT_FILE_CRC_MISMATCH = 14, - RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15, - RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16, - RESULT_FS_FILE_RENAME_FAIL = 17, - RESULT_FS_FILE_DELETE_FAIL = 18, - RESULT_FS_CREATE_DIRECTORY_FAIL = 19, - RESULT_FS_ITER_DIRECTORY_FAIL = 20, - RESULT_CURL_INIT_FAIL = 21, - RESULT_CURL_SET_ERRBUFF_FAIL = 22, - RESULT_CURL_SET_OPTION_FAIL = 23, - RESULT_CURL_SET_PROXY_FAIL = 24, - RESULT_CURL_SET_PROXY_TYPE_FAIL = 25, - RESULT_CURL_SET_PROXY_AUTH_FAIL = 26, - RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27, - RESULT_CURL_PERFORM_FAIL = 28, - RESULT_CURL_USER_CANCEL = 29, - RESULT_GUI_WINDOW_INIT_FAIL = 30, - RESULT_NO_UPDATE_NECESSARY = 31, - RESULT_LO_MISMATCH = 32, - RESULT_NO_MEM = 33, - RESULT_INVALID_ARGS = 34, - RESULT_NETWORK_FAIL = 35, - RESULT_NO_INTERNET_CONNECTION = 36, - RESULT_NO_TAG_MAP = 37, - RESULT_PLUGINS_FULL = 38, - RESULT_PLUGIN_BEFORE_MASTER = 39, - RESULT_INVALID_SYNTAX = 40 - }; - - enum GameIDs { - AUTODETECT = 0, - OBLIVION = 1, - SKYRIM = 3, - FALLOUT3 = 4, - FALLOUTNV = 5 - }; - }; - private: void syncLoadOrder(); @@ -353,9 +276,6 @@ private: void testMasters(); - boss_db initBoss(); - void convertPluginListForBoss(boss_db db, boost::ptr_vector &inputPlugins, std::vector &activePlugins); - private: std::vector m_ESPs; @@ -381,7 +301,6 @@ private: SignalRefreshed m_Refreshed; - BossDLL *m_BOSS; QTemporaryFile m_TempFile; }; diff --git a/src/profile.h b/src/profile.h index 4960671a..5aa77357 100644 --- a/src/profile.h +++ b/src/profile.h @@ -159,6 +159,11 @@ public: */ QString getLockedOrderFileName() const; + /** + * @return the path of the modlist file in this profile + */ + QString getModlistFileName() const; + /** * @return path of the archives file in this profile */ @@ -301,7 +306,6 @@ private: void updateIndices(); - QString getModlistFileName() const; void copyFilesTo(QString &target) const; std::vector splitDZString(const wchar_t *buffer) const; diff --git a/src/resources.qrc b/src/resources.qrc index 73921f64..bf53ea95 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -52,5 +52,8 @@ resources/x-office-calendar.png resources/dialog-warning_16.png resources/mail-attachment.png + resources/document-save_32.png + resources/edit-undo.png + resources/arrange-boxes.png diff --git a/src/resources/arrange-boxes.png b/src/resources/arrange-boxes.png new file mode 100644 index 00000000..b1ab67cf Binary files /dev/null and b/src/resources/arrange-boxes.png differ diff --git a/src/resources/document-save_32.png b/src/resources/document-save_32.png new file mode 100644 index 00000000..db5c52b7 Binary files /dev/null and b/src/resources/document-save_32.png differ diff --git a/src/resources/edit-undo.png b/src/resources/edit-undo.png new file mode 100644 index 00000000..61b2ce9a Binary files /dev/null and b/src/resources/edit-undo.png differ diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index dbc10791..902b4d9c 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -17,62 +17,67 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "selectiondialog.h" -#include "ui_selectiondialog.h" - -#include - -SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) - : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) -{ - ui->setupUi(this); - - ui->descriptionLabel->setText(description); -} - -SelectionDialog::~SelectionDialog() -{ - delete ui; -} - - -void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) -{ - QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - - -QVariant SelectionDialog::getChoiceData() -{ - return m_Choice->property("data"); -} - - -QString SelectionDialog::getChoiceString() -{ - if ((m_Choice == NULL) || - (m_ValidateByData && !m_Choice->property("data").isValid())) { - return QString(); - } else { - return m_Choice->text(); - } -} - - -void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) -{ - m_Choice = button; - if (!m_ValidateByData || m_Choice->property("data").isValid()) { - this->accept(); - } else { - this->reject(); - } -} - -void SelectionDialog::on_cancelButton_clicked() -{ - this->reject(); -} +#include "selectiondialog.h" +#include "ui_selectiondialog.h" + +#include + +SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) + : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) +{ + ui->setupUi(this); + + ui->descriptionLabel->setText(description); +} + +SelectionDialog::~SelectionDialog() +{ + delete ui; +} + + +void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) +{ + QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); + button->setProperty("data", data); + ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); + if (data.isValid()) m_ValidateByData = true; +} + +int SelectionDialog::numChoices() const +{ + return ui->buttonBox->findChildren(QString()).count(); +} + + +QVariant SelectionDialog::getChoiceData() +{ + return m_Choice->property("data"); +} + + +QString SelectionDialog::getChoiceString() +{ + if ((m_Choice == NULL) || + (m_ValidateByData && !m_Choice->property("data").isValid())) { + return QString(); + } else { + return m_Choice->text(); + } +} + + +void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + m_Choice = button; + if (!m_ValidateByData || m_Choice->property("data").isValid()) { + this->accept(); + } else { + this->reject(); + } +} + +void SelectionDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/selectiondialog.h b/src/selectiondialog.h index a2844e97..fc04291e 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -17,50 +17,52 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include -#include - -namespace Ui { -class SelectionDialog; -} - -class SelectionDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit SelectionDialog(const QString &description, QWidget *parent = 0); - - ~SelectionDialog(); - - /** - * @brief add a choice to the dialog - * @param buttonText the text to be displayed on the button - * @param description the description that shows up under in small letters inside the button - * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) - * all buttons that contain no data will be treated as "cancel" buttons - */ - void addChoice(const QString &buttonText, const QString &description, const QVariant &data); - - QVariant getChoiceData(); - QString getChoiceString(); - -private slots: - - void on_buttonBox_clicked(QAbstractButton *button); - - void on_cancelButton_clicked(); - -private: - - Ui::SelectionDialog *ui; - QAbstractButton *m_Choice; - bool m_ValidateByData; - -}; - -#endif // SELECTIONDIALOG_H +#ifndef SELECTIONDIALOG_H +#define SELECTIONDIALOG_H + +#include +#include + +namespace Ui { +class SelectionDialog; +} + +class SelectionDialog : public QDialog +{ + Q_OBJECT + +public: + + explicit SelectionDialog(const QString &description, QWidget *parent = 0); + + ~SelectionDialog(); + + /** + * @brief add a choice to the dialog + * @param buttonText the text to be displayed on the button + * @param description the description that shows up under in small letters inside the button + * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) + * all buttons that contain no data will be treated as "cancel" buttons + */ + void addChoice(const QString &buttonText, const QString &description, const QVariant &data); + + int numChoices() const; + + QVariant getChoiceData(); + QString getChoiceString(); + +private slots: + + void on_buttonBox_clicked(QAbstractButton *button); + + void on_cancelButton_clicked(); + +private: + + Ui::SelectionDialog *ui; + QAbstractButton *m_Choice; + bool m_ValidateByData; + +}; + +#endif // SELECTIONDIALOG_H diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index 00bb42fd..b580a226 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -172,6 +172,14 @@ std::wstring GameInfo::getLogDir() const } +std::wstring GameInfo::getLootDir() const +{ + std::wostringstream temp; + temp << m_OrganizerDirectory << "\\loot"; + return temp.str(); +} + + std::wstring GameInfo::getTutorialDir() const { std::wostringstream temp; diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 10775e6c..89c9402d 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -113,6 +113,7 @@ public: virtual std::wstring getCacheDir() const; virtual std::wstring getOverwriteDir() const; virtual std::wstring getLogDir() const; + virtual std::wstring getLootDir() const; virtual std::wstring getTutorialDir() const; virtual bool requiresBSAInvalidation() const { return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index b1c5e963..6adafba0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -36,10 +36,23 @@ using namespace MOShared; static const int BUFSIZE = 4096; -bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, HANDLE& processHandle, HANDLE& threadHandle) +bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, + HANDLE stdOut, HANDLE stdErr, + HANDLE& processHandle, HANDLE& threadHandle) { + BOOL inheritHandles = FALSE; STARTUPINFO si; ::ZeroMemory(&si, sizeof(si)); + if (stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + if (stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } si.cb = sizeof(si); int length = wcslen(binary) + wcslen(arguments) + 4; wchar_t *commandLine = NULL; @@ -74,7 +87,7 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus BOOL success = ::CreateProcess(NULL, commandLine, NULL, NULL, // no special process or thread attributes - FALSE, // don't inherit handle + inheritHandles, // inherit handles if we plan to use stdout or stderr reroute suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL NULL, // same environment as parent currentDirectory, // current directory @@ -95,14 +108,22 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus } -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString& profileName, int logLevel, const QDir ¤tDirectory, bool hooked) +HANDLE startBinary(const QFileInfo &binary, + const QString &arguments, + const QString& profileName, + int logLevel, + const QDir ¤tDirectory, + bool hooked, + HANDLE stdOut, + HANDLE stdErr) { 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)) { + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, + stdOut, stdErr, processHandle, threadHandle)) { reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); return INVALID_HANDLE_VALUE; } diff --git a/src/spawn.h b/src/spawn.h index 48320fea..3f037119 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -52,11 +52,17 @@ private: * @param arguments arguments to pass to the binary * @param profileName name of the active profile * @param currentDirectory the directory to use as the working directory to run in + * @param logLevel log level to be used by the hook library. Ignored if hooked is false * @param hooked if set, the binary is started with mo injected + * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process + * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process * @return the process handle * @todo is the profile name even used any more? * @todo is the hooked parameter used? **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, const QDir ¤tDirectory, bool hooked); +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, + const QDir ¤tDirectory, bool hooked, + HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); #endif // SPAWN_H + -- cgit v1.3.1