summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt8
-rw-r--r--src/CMakeLists.txt37
-rw-r--r--src/bbcode.cpp9
-rw-r--r--src/browserdialog.cpp12
-rw-r--r--src/categories.cpp17
-rw-r--r--src/directoryrefresher.cpp4
-rw-r--r--src/downloadlist.cpp5
-rw-r--r--src/downloadlistwidget.cpp5
-rw-r--r--src/downloadmanager.cpp67
-rw-r--r--src/editexecutablesdialog.cpp19
-rw-r--r--src/env.cpp479
-rw-r--r--src/env.h154
-rw-r--r--src/envmetrics.cpp254
-rw-r--r--src/envmetrics.h72
-rw-r--r--src/envmodule.cpp376
-rw-r--r--src/envmodule.h98
-rw-r--r--src/envsecurity.cpp400
-rw-r--r--src/envsecurity.h49
-rw-r--r--src/envshortcut.cpp374
-rw-r--r--src/envshortcut.h105
-rw-r--r--src/envwindows.cpp237
-rw-r--r--src/envwindows.h106
-rw-r--r--src/executableslist.cpp50
-rw-r--r--src/executableslist.h4
-rw-r--r--src/filerenamer.cpp52
-rw-r--r--src/filterwidget.cpp5
-rw-r--r--src/forcedloaddialogwidget.cpp9
-rw-r--r--src/icondelegate.cpp4
-rw-r--r--src/installationmanager.cpp25
-rw-r--r--src/instancemanager.cpp16
-rw-r--r--src/loadmechanism.cpp13
-rw-r--r--src/logbuffer.cpp278
-rw-r--r--src/logbuffer.h95
-rw-r--r--src/loglist.cpp254
-rw-r--r--src/loglist.h79
-rw-r--r--src/main.cpp279
-rw-r--r--src/mainwindow.cpp295
-rw-r--r--src/mainwindow.h13
-rw-r--r--src/mainwindow.ui88
-rw-r--r--src/messagedialog.cpp6
-rw-r--r--src/moapplication.cpp15
-rw-r--r--src/modflagicondelegate.cpp4
-rw-r--r--src/modinfo.cpp17
-rw-r--r--src/modinfodialog.cpp16
-rw-r--r--src/modinfodialogconflicts.cpp30
-rw-r--r--src/modinfodialogesps.cpp7
-rw-r--r--src/modinfodialogfiletree.cpp21
-rw-r--r--src/modinfodialogimages.cpp9
-rw-r--r--src/modinfodialognexus.cpp5
-rw-r--r--src/modinforegular.cpp25
-rw-r--r--src/modlist.cpp26
-rw-r--r--src/modlistsortproxy.cpp10
-rw-r--r--src/nexusinterface.cpp63
-rw-r--r--src/nxmaccessmanager.cpp6
-rw-r--r--src/organizercore.cpp158
-rw-r--r--src/organizercore.h11
-rw-r--r--src/overwriteinfodialog.cpp6
-rw-r--r--src/persistentcookiejar.cpp10
-rw-r--r--src/plugincontainer.cpp30
-rw-r--r--src/pluginlist.cpp20
-rw-r--r--src/problemsdialog.cpp2
-rw-r--r--src/profile.cpp51
-rw-r--r--src/profilesdialog.cpp4
-rw-r--r--src/qtgroupingproxy.cpp87
-rw-r--r--src/selfupdater.cpp18
-rw-r--r--src/settings.cpp56
-rw-r--r--src/settings.h10
-rw-r--r--src/settingsdialog.ui20
-rw-r--r--src/shared/directoryentry.cpp23
-rw-r--r--src/shared/error_report.cpp45
-rw-r--r--src/shared/error_report.h19
-rw-r--r--src/shared/util.cpp1833
-rw-r--r--src/shared/util.h394
-rw-r--r--src/syncoverwritedialog.cpp3
-rw-r--r--src/texteditor.cpp7
-rw-r--r--src/transfersavesdialog.cpp13
-rw-r--r--src/usvfsconnector.cpp86
-rw-r--r--src/usvfsconnector.h7
78 files changed, 4189 insertions, 3430 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94c76373..ac9d8fc7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
-ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>)
+ADD_COMPILE_OPTIONS(
+ $<$<CXX_COMPILER_ID:MSVC>:/MP>
+ $<$<CXX_COMPILER_ID:MSVC>:/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING>
+ $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>>
+ $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>)
PROJECT(organizer)
@@ -11,9 +15,11 @@ set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
set(CMAKE_INSTALL_MESSAGE NEVER)
SET(DEPENDENCIES_DIR CACHE PATH "")
+
# hint to find qt in dependencies path
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll)
+LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build)
ADD_SUBDIRECTORY(src)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f197211a..9785dc3d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -73,7 +73,7 @@ SET(organizer_SRCS
mainwindow.cpp
main.cpp
loghighlighter.cpp
- logbuffer.cpp
+ loglist.cpp
lockeddialogbase.cpp
lockeddialog.cpp
waitingonclosedialog.cpp
@@ -129,6 +129,12 @@ SET(organizer_SRCS
filerenamer.cpp
texteditor.cpp
expanderwidget.cpp
+ env.cpp
+ envmetrics.cpp
+ envmodule.cpp
+ envsecurity.cpp
+ envshortcut.cpp
+ envwindows.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -181,7 +187,7 @@ SET(organizer_HDRS
messagedialog.h
mainwindow.h
loghighlighter.h
- logbuffer.h
+ loglist.h
lockeddialogbase.h
lockeddialog.h
waitingonclosedialog.h
@@ -239,6 +245,12 @@ SET(organizer_HDRS
filerenamer.h
texteditor.h
expanderwidget.h
+ env.h
+ envmetrics.h
+ envmodule.h
+ envsecurity.h
+ envshortcut.h
+ envwindows.h
shared/windows_error.h
shared/error_report.h
@@ -350,6 +362,15 @@ set(downloads
downloadmanager
)
+set(env
+ env
+ envmetrics
+ envmodule
+ envsecurity
+ envshortcut
+ envwindows
+)
+
set(executables
executableslist
editexecutablesdialog
@@ -436,7 +457,7 @@ set(widgets
filterwidget
icondelegate
lcdnumber
- logbuffer
+ loglist
loghighlighter
modflagicondelegate
modidlineedit
@@ -447,8 +468,8 @@ set(widgets
)
set(src_filters
- application core browser dialogs downloads executables locking modinfo modinfo\\dialog
- modlist plugins previews profiles settings utilities widgets
+ application core browser dialogs downloads env executables locking modinfo
+ modinfo\\dialog modlist plugins previews profiles settings utilities widgets
)
foreach(filter in list ${src_filters})
@@ -520,6 +541,9 @@ LINK_DIRECTORIES(${Boost_LIBRARY_DIRS})
FIND_PACKAGE(zlib REQUIRED)
# TODO FindZlib doesn't find the static zlib library
+# fmt
+find_package(fmt REQUIRED)
+
INCLUDE_DIRECTORIES(${project_path}/uibase/src
${project_path}/bsatk/src
${project_path}/esptk/src
@@ -551,10 +575,11 @@ ELSE()
ENDIF()
ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm})
+
TARGET_LINK_LIBRARIES(ModOrganizer
Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick
Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets
- ${Boost_LIBRARIES}
+ ${Boost_LIBRARIES} fmt::fmt
zlibstatic
uibase esptk bsatk githubpp
${usvfs_name}
diff --git a/src/bbcode.cpp b/src/bbcode.cpp
index 323dd128..d9b7debd 100644
--- a/src/bbcode.cpp
+++ b/src/bbcode.cpp
@@ -18,13 +18,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "bbcode.h"
-
+#include <log.h>
#include <QRegExp>
#include <map>
-
namespace BBCode {
+namespace log = MOBase::log;
class BBCodeMap {
@@ -88,7 +88,7 @@ public:
return temp.replace(tagIter->second.first, QString("<font style=\"color: #%1;\">%2</font>").arg(color, content));
}
} else {
- qWarning("don't know how to deal with tag %s", qUtf8Printable(tagName));
+ log::warn("don't know how to deal with tag {}", tagName);
}
} else {
if (tagName == "*") {
@@ -99,8 +99,7 @@ public:
} else {
// expression doesn't match. either the input string is invalid
// or the expression is
- qWarning("%s doesn't match the expression for %s",
- qUtf8Printable(temp), qUtf8Printable(tagName));
+ log::warn("{} doesn't match the expression for {}", temp, tagName);
length = 0;
return QString();
}
diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp
index 70190433..73a6a2d0 100644
--- a/src/browserdialog.cpp
+++ b/src/browserdialog.cpp
@@ -24,9 +24,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "messagedialog.h"
#include "report.h"
#include "persistentcookiejar.h"
+#include "settings.h"
#include <utility.h>
-#include "settings.h"
+#include <log.h>
#include <QWebEngineSettings>
#include <QNetworkCookieJar>
@@ -37,6 +38,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDir>
#include <QKeyEvent>
+using namespace MOBase;
BrowserDialog::BrowserDialog(QWidget *parent)
@@ -190,12 +192,12 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply)
try {
QWebEnginePage *page = qobject_cast<QWebEnginePage*>(sender());
if (page == nullptr) {
- qCritical("sender not a page");
+ log::error("sender not a page");
return;
}
BrowserView *view = qobject_cast<BrowserView*>(page->view());
if (view == nullptr) {
- qCritical("no view?");
+ log::error("no view?");
return;
}
@@ -204,14 +206,14 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply)
if (isVisible()) {
MessageDialog::showMessage(tr("failed to start download"), this);
}
- qCritical("exception downloading unsupported content: %s", e.what());
+ log::error("exception downloading unsupported content: {}", e.what());
}
}
void BrowserDialog::downloadRequested(const QNetworkRequest &request)
{
- qCritical("download request %s ignored", request.url().toString().toUtf8().constData());
+ log::error("download request {} ignored", request.url().toString());
}
diff --git a/src/categories.cpp b/src/categories.cpp
index 9e5fa9f7..12b18998 100644
--- a/src/categories.cpp
+++ b/src/categories.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <utility.h>
#include <report.h>
+#include <log.h>
#include <QObject>
#include <QFile>
@@ -61,8 +62,9 @@ void CategoryFactory::loadCategories()
++lineNum;
QList<QByteArray> cells = line.split('|');
if (cells.count() != 4) {
- qCritical("invalid category line %d: %s (%d cells)",
- lineNum, line.constData(), cells.count());
+ log::error(
+ "invalid category line {}: {} ({} cells)",
+ lineNum, line.constData(), cells.count());
} else {
std::vector<int> nexusIDs;
if (cells[2].length() > 0) {
@@ -72,7 +74,7 @@ void CategoryFactory::loadCategories()
bool ok = false;
int temp = iter->toInt(&ok);
if (!ok) {
- qCritical("invalid category id %s", iter->constData());
+ log::error("invalid category id {}", iter->constData());
}
nexusIDs.push_back(temp);
}
@@ -82,8 +84,7 @@ void CategoryFactory::loadCategories()
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());
+ log::error("invalid category line {}: {}", lineNum, line.constData());
}
addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID);
}
@@ -294,7 +295,7 @@ bool CategoryFactory::isDecendantOf(int id, int parentID) const
return isDecendantOf(m_Categories[index].m_ParentID, parentID);
}
} else {
- qWarning("%d is no valid category id", id);
+ log::warn("{} is no valid category id", id);
return false;
}
}
@@ -359,10 +360,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const
{
std::map<int, unsigned int>::const_iterator iter = m_NexusMap.find(nexusID);
if (iter != m_NexusMap.end()) {
- qDebug("nexus category id %d maps to internal %d", nexusID, iter->second);
+ log::debug("nexus category id {} maps to internal {}", nexusID, iter->second);
return iter->second;
} else {
- qDebug("nexus category id %d not mapped", nexusID);
+ log::debug("nexus category id {} not mapped", nexusID);
return 0U;
}
}
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp
index 3ce4691b..87305599 100644
--- a/src/directoryrefresher.cpp
+++ b/src/directoryrefresher.cpp
@@ -127,7 +127,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu
FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority);
for (const QString &filename : stealFiles) {
if (filename.isEmpty()) {
- qWarning("Trying to find file with no name");
+ log::warn("Trying to find file with no name");
continue;
}
QFileInfo fileInfo(filename);
@@ -143,7 +143,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu
QString warnStr = fileInfo.absolutePath();
if (warnStr.isEmpty())
warnStr = filename;
- qWarning("file not found: %s", qUtf8Printable(warnStr));
+ log::warn("file not found: {}", warnStr);
}
}
} else {
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp
index 5e698e0e..36bc2b7f 100644
--- a/src/downloadlist.cpp
+++ b/src/downloadlist.cpp
@@ -19,12 +19,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlist.h"
#include "downloadmanager.h"
+#include <log.h>
#include <QEvent>
#include <QColor>
#include <QIcon>
-
#include <QSortFilterProxyModel>
+using namespace MOBase;
DownloadList::DownloadList(DownloadManager *manager, QObject *parent)
: QAbstractTableModel(parent), m_Manager(manager)
@@ -192,7 +193,7 @@ void DownloadList::update(int row)
else if (row < this->rowCount())
emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, this->columnCount(QModelIndex())-1, QModelIndex()));
else
- qCritical("invalid row %d in download list, update failed", row);
+ log::error("invalid row {} in download list, update failed", row);
}
QString DownloadList::sizeFormat(quint64 size) const
diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp
index e2a6f321..85d27831 100644
--- a/src/downloadlistwidget.cpp
+++ b/src/downloadlistwidget.cpp
@@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlist.h"
#include "downloadlistwidget.h"
+#include <log.h>
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
@@ -29,6 +30,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QCheckBox>
#include <QWidgetAction>
+using namespace MOBase;
+
void DownloadProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QModelIndex sourceIndex = m_SortProxy->mapToSource(index);
@@ -286,7 +289,7 @@ void DownloadListWidget::issueDelete()
void DownloadListWidget::issueRemoveFromView()
{
- qDebug() << "removing from view: " << m_ContextRow;
+ log::debug("removing from view: {}", m_ContextRow);
emit removeDownload(m_ContextRow, false);
}
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index ec1faed4..3b084b83 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -352,7 +352,7 @@ void DownloadManager::refreshList()
}
}
if (orphans.size() > 0) {
- qDebug("%d orphaned meta files will be deleted", orphans.size());
+ log::debug("{} orphaned meta files will be deleted", orphans.size());
shellDelete(orphans, true);
}
@@ -378,9 +378,6 @@ void DownloadManager::refreshList()
}
}
- //if (m_ActiveDownloads.size() != downloadsBefore) {
- qDebug("Downloads after refresh: %d", m_ActiveDownloads.size());
- //}
emit update(-1);
//let watcher trigger refreshes again
@@ -401,7 +398,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName,
}
QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit());
- qDebug("selected download url: %s", qUtf8Printable(preferredUrl.toString()));
+ log::debug("selected download url: {}", preferredUrl.toString());
QNetworkRequest request(preferredUrl);
request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent());
return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo);
@@ -562,9 +559,9 @@ void DownloadManager::addNXMDownload(const QString &url)
break;
}
}
- qDebug("add nxm download: %s", qUtf8Printable(url));
+ log::debug("add nxm download: {}", url);
if (foundGame == nullptr) {
- qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game()));
+ log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game());
QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO "
"has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok);
return;
@@ -572,13 +569,14 @@ void DownloadManager::addNXMDownload(const QString &url)
for (auto tuple : m_PendingDownloads) {
if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) {
- QString debugStr("download requested is already queued (mod: %1, file: %2)");
- QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2"));
+ const auto infoStr =
+ tr("There is already a download queued for this file.\n\nMod %1\nFile %2")
+ .arg(nxmInfo.modId()).arg(nxmInfo.fileId());
- debugStr = debugStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId());
- infoStr = infoStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId());
+ log::debug(
+ "download requested is already queued (mod: {}, file: {})",
+ nxmInfo.modId(), nxmInfo.fileId());
- qDebug(qUtf8Printable(debugStr));
QMessageBox::information(nullptr, tr("Already Queued"), infoStr, QMessageBox::Ok);
return;
}
@@ -622,7 +620,7 @@ void DownloadManager::addNXMDownload(const QString &url)
infoStr = infoStr.arg(QStringLiteral("<blank>"));
}
- qDebug(qUtf8Printable(debugStr));
+ log::debug("{}", debugStr);
QMessageBox::information(nullptr, tr("Already Started"), infoStr, QMessageBox::Ok);
return;
}
@@ -660,7 +658,7 @@ void DownloadManager::removeFile(int index, bool deleteFile)
if ((download->m_State == STATE_STARTED) ||
(download->m_State == STATE_DOWNLOADING)) {
// shouldn't have been possible
- qCritical("tried to remove active download");
+ log::error("tried to remove active download");
endDisableDirWatcher();
return;
}
@@ -798,7 +796,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile)
emit update(-1);
endDisableDirWatcher();
} catch (const std::exception &e) {
- qCritical("failed to remove download: %s", e.what());
+ log::error("failed to remove download: {}", e.what());
}
refreshList();
}
@@ -883,7 +881,7 @@ void DownloadManager::resumeDownloadInt(int index)
if (info->m_State == STATE_ERROR) {
info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count();
}
- qDebug("request resume from url %s", qUtf8Printable(info->currentURL()));
+ log::debug("request resume from url {}", info->currentURL());
QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit()));
request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent());
if (info->m_State != STATE_ERROR) {
@@ -896,7 +894,7 @@ void DownloadManager::resumeDownloadInt(int index)
std::get<2>(info->m_SpeedDiff) = 0;
std::get<3>(info->m_SpeedDiff) = 0;
std::get<4>(info->m_SpeedDiff) = 0;
- qDebug("resume at %lld bytes", info->m_ResumePos);
+ log::debug("resume at {} bytes", info->m_ResumePos);
startDownload(m_NexusInterface->getAccessManager()->get(request), info, true);
}
emit update(index);
@@ -924,7 +922,7 @@ void DownloadManager::queryInfo(int index)
DownloadInfo *info = m_ActiveDownloads[index];
if (info->m_FileInfo->repository != "Nexus") {
- qWarning("re-querying file info is currently only possible with Nexus");
+ log::warn("re-querying file info is currently only possible with Nexus");
return;
}
@@ -976,7 +974,7 @@ void DownloadManager::queryInfoMd5(int index)
DownloadInfo *info = m_ActiveDownloads[index];
if (info->m_FileInfo->repository != "Nexus") {
- qWarning("re-querying file info is currently only possible with Nexus");
+ log::warn("re-querying file info is currently only possible with Nexus");
return;
}
@@ -993,11 +991,11 @@ void DownloadManager::queryInfoMd5(int index)
downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName);
}
if (!downloadFile.exists()) {
- qDebug("Can't find download file %s", info->m_FileName);
+ log::debug("Can't find download file {}", info->m_FileName);
return;
}
if (!downloadFile.open(QIODevice::ReadOnly)) {
- qDebug("Can't open download file %s", info->m_FileName);
+ log::debug("Can't open download file {}", info->m_FileName);
return;
}
info->m_Hash = QCryptographicHash::hash(downloadFile.readAll(), QCryptographicHash::Md5);
@@ -1016,7 +1014,7 @@ void DownloadManager::visitOnNexus(int index)
DownloadInfo *info = m_ActiveDownloads[index];
if (info->m_FileInfo->repository != "Nexus") {
- qWarning("Visiting mod page is currently only possible with Nexus");
+ log::warn("Visiting mod page is currently only possible with Nexus");
return;
}
@@ -1389,7 +1387,7 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana
m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString()));
} break;
case STATE_FETCHINGMODINFO_MD5: {
- qDebug(qUtf8Printable(QString("Searching %1 for MD5 of %2").arg(info->m_GamesToQuery[0]).arg(QString(info->m_Hash.toHex()))));
+ log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], QString(info->m_Hash.toHex()));
m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5(info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString()));
} break;
case STATE_READY: {
@@ -1619,8 +1617,9 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian
}
} else {
if (info->m_FileInfo->fileID == 0) {
- qWarning("could not determine file id for %s (state %d)",
- qUtf8Printable(info->m_FileName), info->m_State);
+ log::warn(
+ "could not determine file id for {} (state {})",
+ info->m_FileName, info->m_State);
}
}
@@ -1784,7 +1783,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use
if (chosenIdx < 0) {
chosenIdx = i; //intentional to not break in order to check other results
} else {
- qDebug("Multiple active files found during MD5 search. Defaulting to time stamps...");
+ log::debug("Multiple active files found during MD5 search. Defaulting to time stamps...");
chosenIdx = -1;
break;
}
@@ -2004,7 +2003,7 @@ void DownloadManager::downloadFinished(int index)
resumeDownloadInt(index);
}
} else {
- qWarning("no download index %d", index);
+ log::warn("no download index {}", index);
}
}
@@ -2013,9 +2012,9 @@ void DownloadManager::downloadError(QNetworkReply::NetworkError error)
{
if (error != QNetworkReply::OperationCanceledError) {
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
- qWarning("%s (%d)", reply != nullptr ? qUtf8Printable(reply->errorString())
- : "Download error occured",
- error);
+ log::warn("{} ({})",
+ reply != nullptr ? reply->errorString() : "Download error occured",
+ error);
}
}
@@ -2038,7 +2037,7 @@ void DownloadManager::metaDataChanged()
}
}
} else {
- qWarning("meta data event for unknown download");
+ log::warn("meta data event for unknown download");
}
}
@@ -2073,7 +2072,11 @@ void DownloadManager::writeData(DownloadInfo *info)
if (ret < info->m_Reply->size()) {
QString fileName = info->m_FileName; // m_FileName may be destroyed after setState
setState(info, DownloadState::STATE_CANCELED);
- qCritical(QString("Unable to write download \"%2\" to drive (return %1)").arg(ret).arg(info->m_FileName).toLocal8Bit());
+
+ log::error(
+ "Unable to write download \"{}\" to drive (return {})",
+ info->m_FileName, ret);
+
reportError(tr("Unable to write download to drive (return %1).\n"
"Check the drive's available storage.\n\n"
"Canceling download \"%2\"...").arg(ret).arg(fileName));
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index 8929d207..3ec3d64f 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -101,9 +101,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const
auto itor = m_executablesList.find(title);
if (itor == m_executablesList.end()) {
- qWarning().nospace()
- << "getExecutablesList(): executable '" << title << "' not found";
-
+ log::warn("getExecutablesList(): executable '{}' not found", title);
continue;
}
@@ -293,9 +291,10 @@ void EditExecutablesDialog::setEdits(const Executable& e)
modIndex = ui->mods->findText(modName->value);
if (modIndex == -1) {
- qWarning().nospace()
- << "executable '" << e.title() << "' uses mod '" << modName->value << "' "
- << "as a custom overwrite, but that mod doesn't exist";
+ log::warn(
+ "executable '{}' uses mod '{}' as a custom overwrite, but that mod "
+ "doesn't exist",
+ e.title(), modName->value);
}
}
@@ -335,7 +334,7 @@ void EditExecutablesDialog::save()
auto* e = selectedExe();
if (!e) {
- qWarning("trying to save but nothing is selected");
+ log::warn("trying to save but nothing is selected");
return;
}
@@ -475,13 +474,13 @@ void EditExecutablesDialog::on_remove_clicked()
{
auto* item = selectedItem();
if (!item) {
- qWarning("trying to remove entry but nothing is selected");
+ log::warn("trying to remove entry but nothing is selected");
return;
}
auto* exe = selectedExe();
if (!exe) {
- qWarning("trying to remove entry but nothing is selected");
+ log::warn("trying to remove entry but nothing is selected");
return;
}
@@ -646,7 +645,7 @@ void EditExecutablesDialog::on_configureLibraries_clicked()
{
auto* e = selectedExe();
if (!e) {
- qWarning("trying to configure libraries but nothing is selected");
+ log::warn("trying to configure libraries but nothing is selected");
return;
}
diff --git a/src/env.cpp b/src/env.cpp
new file mode 100644
index 00000000..641eb4a7
--- /dev/null
+++ b/src/env.cpp
@@ -0,0 +1,479 @@
+#include "env.h"
+#include "envmetrics.h"
+#include "envmodule.h"
+#include "envsecurity.h"
+#include "envshortcut.h"
+#include "envwindows.h"
+#include <log.h>
+#include <utility.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+Console::Console()
+ : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr)
+{
+ // open a console
+ if (!AllocConsole()) {
+ // failed, ignore
+ }
+
+ m_hasConsole = true;
+
+ // redirect stdin, stdout and stderr to it
+ freopen_s(&m_in, "CONIN$", "r", stdin);
+ freopen_s(&m_out, "CONOUT$", "w", stdout);
+ freopen_s(&m_err, "CONOUT$", "w", stderr);
+}
+
+Console::~Console()
+{
+ // close redirected handles and redirect standard stream to NUL in case
+ // they're used after this
+
+ if (m_err) {
+ std::fclose(m_err);
+ freopen_s(&m_err, "NUL", "w", stderr);
+ }
+
+ if (m_out) {
+ std::fclose(m_out);
+ freopen_s(&m_out, "NUL", "w", stdout);
+ }
+
+ if (m_in) {
+ std::fclose(m_in);
+ freopen_s(&m_in, "NUL", "r", stdin);
+ }
+
+ // close console
+ if (m_hasConsole) {
+ FreeConsole();
+ }
+}
+
+
+Environment::Environment()
+ : m_windows(new WindowsInfo), m_metrics(new Metrics)
+{
+ m_modules = getLoadedModules();
+ m_security = getSecurityProducts();
+}
+
+// anchor
+Environment::~Environment() = default;
+
+const std::vector<Module>& Environment::loadedModules() const
+{
+ return m_modules;
+}
+
+const WindowsInfo& Environment::windowsInfo() const
+{
+ return *m_windows;
+}
+
+const std::vector<SecurityProduct>& Environment::securityProducts() const
+{
+ return m_security;
+}
+
+const Metrics& Environment::metrics() const
+{
+ return *m_metrics;
+}
+
+void Environment::dump() const
+{
+ log::debug("windows: {}", m_windows->toString());
+
+ if (m_windows->compatibilityMode()) {
+ log::warn("MO seems to be running in compatibility mode");
+ }
+
+ log::debug("security products:");
+ for (const auto& sp : m_security) {
+ log::debug(" . {}", sp.toString());
+ }
+
+ log::debug("modules loaded in process:");
+ for (const auto& m : m_modules) {
+ log::debug(" . {}", m.toString());
+ }
+
+ log::debug("displays:");
+ for (const auto& d : m_metrics->displays()) {
+ log::debug(" . {}", d.toString());
+ }
+}
+
+
+struct Process
+{
+ std::wstring filename;
+ DWORD pid;
+
+ Process(std::wstring f, DWORD id)
+ : filename(std::move(f)), pid(id)
+ {
+ }
+};
+
+
+// returns the filename of the given process or the current one
+//
+std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
+{
+ // double the buffer size 10 times
+ const int MaxTries = 10;
+
+ DWORD bufferSize = MAX_PATH;
+
+ for (int tries=0; tries<MaxTries; ++tries)
+ {
+ auto buffer = std::make_unique<wchar_t[]>(bufferSize + 1);
+ std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0);
+
+ DWORD writtenSize = 0;
+
+ if (process == INVALID_HANDLE_VALUE) {
+ // query this process
+ writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize);
+ } else {
+ // query another process
+ writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize);
+ }
+
+ if (writtenSize == 0) {
+ // hard failure
+ const auto e = GetLastError();
+ std::wcerr << formatSystemMessage(e) << L"\n";
+ break;
+ } else if (writtenSize >= bufferSize) {
+ // buffer is too small, try again
+ bufferSize *= 2;
+ } else {
+ // if GetModuleFileName() works, `writtenSize` does not include the null
+ // terminator
+ const std::wstring s(buffer.get(), writtenSize);
+ const std::filesystem::path path(s);
+
+ return path.filename().native();
+ }
+ }
+
+ // something failed or the path is way too long to make sense
+
+ std::wstring what;
+ if (process == INVALID_HANDLE_VALUE) {
+ what = L"the current process";
+ } else {
+ what = L"pid " + std::to_wstring(reinterpret_cast<std::uintptr_t>(process));
+ }
+
+ std::wcerr << L"failed to get filename for " << what << L"\n";
+ return {};
+}
+
+std::vector<DWORD> runningProcessesIds()
+{
+ // double the buffer size 10 times
+ const int MaxTries = 10;
+
+ // initial size of 300 processes, unlikely to be more than that
+ std::size_t size = 300;
+
+ for (int tries=0; tries<MaxTries; ++tries) {
+ auto ids = std::make_unique<DWORD[]>(size);
+ std::fill(ids.get(), ids.get() + size, 0);
+
+ DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0]));
+ DWORD bytesWritten = 0;
+
+ if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten))
+ {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to enumerate processes, "
+ << formatSystemMessage(e) << L"\n";
+
+ return {};
+ }
+
+ if (bytesWritten == bytesGiven) {
+ // no way to distinguish between an exact fit and not enough space,
+ // just try again
+ size *= 2;
+ continue;
+ }
+
+ const auto count = bytesWritten / sizeof(ids[0]);
+ return std::vector<DWORD>(ids.get(), ids.get() + count);
+ }
+
+ std::cerr << L"too many processes to enumerate";
+ return {};
+}
+
+std::vector<Process> runningProcesses()
+{
+ const auto pids = runningProcessesIds();
+ std::vector<Process> v;
+
+ for (const auto& pid : pids) {
+ if (pid == 0) {
+ // the idle process has pid 0 and seems to be picked up by EnumProcesses()
+ continue;
+ }
+
+ HandlePtr h(OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
+
+ if (!h) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_ACCESS_DENIED) {
+ // don't log access denied, will happen a lot for system processes, even
+ // when elevated
+ std::wcerr
+ << L"failed to open process " << pid << L", "
+ << formatSystemMessage(e) << L"\n";
+ }
+
+ continue;
+ }
+
+ auto filename = processFilename(h.get());
+ if (!filename.empty()) {
+ v.emplace_back(std::move(filename), pid);
+ }
+ }
+
+ return v;
+}
+
+DWORD findOtherPid()
+{
+ const std::wstring defaultName = L"ModOrganizer.exe";
+
+ std::wclog << L"looking for the other process...\n";
+
+ // used to skip the current process below
+ const auto thisPid = GetCurrentProcessId();
+ std::wclog << L"this process id is " << thisPid << L"\n";
+
+ // getting the filename for this process, assumes the other process has the
+ // smae one
+ auto filename = processFilename();
+ if (filename.empty()) {
+ std::wcerr
+ << L"can't get current process filename, defaulting to "
+ << defaultName << L"\n";
+
+ filename = defaultName;
+ } else {
+ std::wclog << L"this process filename is " << filename << L"\n";
+ }
+
+ // getting all running processes
+ const auto processes = runningProcesses();
+ std::wclog << L"there are " << processes.size() << L" processes running\n";
+
+ // going through processes, trying to find one with the same name and a
+ // different pid than this process has
+ for (const auto& p : processes) {
+ if (p.filename == filename) {
+ if (p.pid != thisPid) {
+ return p.pid;
+ }
+ }
+ }
+
+ std::wclog
+ << L"no process with this filename\n"
+ << L"MO may not be running, or it may be running as administrator\n"
+ << L"you can try running this again as administrator\n";
+
+ return 0;
+}
+
+std::wstring tempDir()
+{
+ const DWORD bufferSize = MAX_PATH + 1;
+ wchar_t buffer[bufferSize + 1] = {};
+
+ const auto written = GetTempPathW(bufferSize, buffer);
+ if (written == 0) {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to get temp path, " << formatSystemMessage(e) << L"\n";
+
+ return {};
+ }
+
+ // `written` does not include the null terminator
+ return std::wstring(buffer, buffer + written);
+}
+
+HandlePtr tempFile(const std::wstring dir)
+{
+ // maximum tries of incrementing the counter
+ const int MaxTries = 100;
+
+ // UTC time and date will be in the filename
+ const auto now = std::time(0);
+ const auto tm = std::gmtime(&now);
+
+ // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where
+ // i can go until MaxTries
+ std::wostringstream oss;
+ oss
+ << L"ModOrganizer-"
+ << std::setw(4) << (1900 + tm->tm_year)
+ << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1)
+ << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T"
+ << std::setw(2) << std::setfill(L'0') << tm->tm_hour
+ << std::setw(2) << std::setfill(L'0') << tm->tm_min
+ << std::setw(2) << std::setfill(L'0') << tm->tm_sec;
+
+ const std::wstring prefix = oss.str();
+ const std::wstring ext = L".dmp";
+
+ // first path to try, without counter in it
+ std::wstring path = dir + L"\\" + prefix + ext;
+
+ for (int i=0; i<MaxTries; ++i) {
+ std::wclog << L"trying file '" << path << L"'\n";
+
+ HandlePtr h (CreateFileW(
+ path.c_str(), GENERIC_WRITE, 0, nullptr,
+ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
+
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ // worked
+ return h;
+ }
+
+ const auto e = GetLastError();
+
+ if (e != ERROR_FILE_EXISTS) {
+ // probably no write access
+ std::wcerr
+ << L"failed to create dump file, " << formatSystemMessage(e) << L"\n";
+
+ return {};
+ }
+
+ // try again with "-i"
+ path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext;
+ }
+
+ std::wcerr << L"can't create dump file, ran out of filenames\n";
+ return {};
+}
+
+HandlePtr dumpFile()
+{
+ // try the current directory
+ HandlePtr h = tempFile(L".");
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ return h;
+ }
+
+ std::wclog << L"cannot write dump file in current directory\n";
+
+ // try the temp directory
+ const auto dir = tempDir();
+
+ if (!dir.empty()) {
+ h = tempFile(dir.c_str());
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ return h;
+ }
+ }
+
+ return {};
+}
+
+bool createMiniDump(HANDLE process, CoreDumpTypes type)
+{
+ const DWORD pid = GetProcessId(process);
+
+ const HandlePtr file = dumpFile();
+ if (!file) {
+ std::wcerr << L"nowhere to write the dump file\n";
+ return false;
+ }
+
+ auto flags = _MINIDUMP_TYPE(
+ MiniDumpNormal |
+ MiniDumpWithHandleData |
+ MiniDumpWithUnloadedModules |
+ MiniDumpWithProcessThreadData);
+
+ if (type == CoreDumpTypes::Data) {
+ std::wclog << L"writing minidump with data\n";
+ flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs);
+ } else if (type == CoreDumpTypes::Full) {
+ std::wclog << L"writing full minidump\n";
+ flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory);
+ } else {
+ std::wclog << L"writing mini minidump\n";
+ }
+
+ const auto ret = MiniDumpWriteDump(
+ process, pid, file.get(), flags, nullptr, nullptr, nullptr);
+
+ if (!ret) {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n";
+
+ return false;
+ }
+
+ std::wclog << L"minidump written correctly\n";
+ return true;
+}
+
+
+bool coredump(CoreDumpTypes type)
+{
+ std::wclog << L"creating minidump for the current process\n";
+ return createMiniDump(GetCurrentProcess(), type);
+}
+
+bool coredumpOther(CoreDumpTypes type)
+{
+ std::wclog << L"creating minidump for an running process\n";
+
+ const auto pid = findOtherPid();
+ if (pid == 0) {
+ std::wcerr << L"no other process found\n";
+ return false;
+ }
+
+ std::wclog << L"found other process with pid " << pid << L"\n";
+
+ HandlePtr handle(OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
+
+ if (!handle) {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to open process " << pid << L", "
+ << formatSystemMessage(e) << L"\n";
+
+ return false;
+ }
+
+ return createMiniDump(handle.get(), type);
+}
+
+} // namespace
diff --git a/src/env.h b/src/env.h
new file mode 100644
index 00000000..0e88263b
--- /dev/null
+++ b/src/env.h
@@ -0,0 +1,154 @@
+namespace env
+{
+
+class Module;
+class SecurityProduct;
+class WindowsInfo;
+class Metrics;
+
+
+// used by HandlePtr, calls CloseHandle() as the deleter
+//
+struct HandleCloser
+{
+ using pointer = HANDLE;
+
+ void operator()(HANDLE h)
+ {
+ if (h != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(h);
+ }
+ }
+};
+
+using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>;
+
+
+// used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter
+//
+struct DesktopDCReleaser
+{
+ using pointer = HDC;
+
+ void operator()(HDC dc)
+ {
+ if (dc != 0) {
+ ::ReleaseDC(0, dc);
+ }
+ }
+};
+
+using DesktopDCPtr = std::unique_ptr<HDC, DesktopDCReleaser>;
+
+
+// used by LibraryPtr, calls FreeLibrary as the deleter
+//
+struct LibraryFreer
+{
+ using pointer = HINSTANCE;
+
+ void operator()(HINSTANCE h)
+ {
+ if (h != 0) {
+ ::FreeLibrary(h);
+ }
+ }
+};
+
+using LibraryPtr = std::unique_ptr<HINSTANCE, LibraryFreer>;
+
+
+// used by COMPtr, calls Release() as the deleter
+//
+struct COMReleaser
+{
+ void operator()(IUnknown* p)
+ {
+ if (p) {
+ p->Release();
+ }
+ }
+};
+
+template <class T>
+using COMPtr = std::unique_ptr<T, COMReleaser>;
+
+
+// creates a console in the constructor and destroys it in the destructor,
+// also redirects standard streams
+//
+class Console
+{
+public:
+ // opens the console and redirects standard streams to it
+ //
+ Console();
+
+ // destroys the console and redirects the standard stream to NUL
+ //
+ ~Console();
+
+private:
+ // whether the console was allocated successfully
+ bool m_hasConsole;
+
+ // standard streams
+ FILE* m_in;
+ FILE* m_out;
+ FILE* m_err;
+};
+
+
+// represents the process's environment
+//
+class Environment
+{
+public:
+ Environment();
+ ~Environment();
+
+ // list of loaded modules in the current process
+ //
+ const std::vector<Module>& loadedModules() const;
+
+ // information about the operating system
+ //
+ const WindowsInfo& windowsInfo() const;
+
+ // information about the installed security products
+ //
+ const std::vector<SecurityProduct>& securityProducts() const;
+
+ // information about displays
+ //
+ const Metrics& metrics() const;
+
+ // logs the environment
+ //
+ void dump() const;
+
+private:
+ std::vector<Module> m_modules;
+ std::unique_ptr<WindowsInfo> m_windows;
+ std::vector<SecurityProduct> m_security;
+ std::unique_ptr<Metrics> m_metrics;
+};
+
+
+enum class CoreDumpTypes
+{
+ Mini = 1,
+ Data,
+ Full
+};
+
+// creates a minidump file for the given process
+//
+bool coredump(CoreDumpTypes type);
+
+// finds another process with the same name as this one and creates a minidump
+// file for it
+//
+bool coredumpOther(CoreDumpTypes type);
+
+} // namespace env
diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp
new file mode 100644
index 00000000..b1b9bd2e
--- /dev/null
+++ b/src/envmetrics.cpp
@@ -0,0 +1,254 @@
+#include "envmetrics.h"
+#include "env.h"
+#include <Windows.h>
+#include <shellscalingapi.h>
+#include <log.h>
+#include <utility.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+// fallback for windows 7
+//
+int getDesktopDpi()
+{
+ // desktop DC
+ DesktopDCPtr dc(GetDC(0));
+
+ if (!dc) {
+ const auto e = GetLastError();
+ log::error("can't get desktop DC, {}", formatSystemMessage(e));
+ return 0;
+ }
+
+ return GetDeviceCaps(dc.get(), LOGPIXELSX);
+}
+
+// finds a monitor by device name; there's no real good way to do that except
+// by enumerating all the monitors and checking their name
+//
+HMONITOR findMonitor(const QString& name)
+{
+ // passed to the enumeration callback
+ struct Data
+ {
+ QString name;
+ HMONITOR hm;
+ };
+
+ Data data = {name, 0};
+
+ // callback
+ auto callback = [](HMONITOR hm, HDC, RECT*, LPARAM lp) {
+ auto& data = *reinterpret_cast<Data*>(lp);
+
+ MONITORINFOEX mi = {};
+ mi.cbSize = sizeof(mi);
+
+ // monitor info will include the name
+ if (!GetMonitorInfoW(hm, &mi)) {
+ const auto e = GetLastError();
+ log::error(
+ "GetMonitorInfo() failed for '{}', {}",
+ data.name, formatSystemMessage(e));
+
+ // error for this monitor, but continue
+ return TRUE;
+ }
+
+ if (QString::fromWCharArray(mi.szDevice) == data.name) {
+ // found, stop
+ data.hm = hm;
+ return FALSE;
+ }
+
+ // not found, continue to the next monitor
+ return TRUE;
+ };
+
+
+ // for each monitor
+ EnumDisplayMonitors(0, nullptr, callback, reinterpret_cast<LPARAM>(&data));
+
+ return data.hm;
+}
+
+// returns the dpi for the given monitor; for systems that do not support
+// per-monitor dpi (such as windows 7), this is the desktop dpi
+//
+int getDpi(const QString& monitorDevice)
+{
+ using GetDpiForMonitorFunction =
+ HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
+
+ static LibraryPtr shcore;
+ static GetDpiForMonitorFunction* GetDpiForMonitor = nullptr;
+ static bool checked = false;
+
+ if (!checked) {
+ // try to find GetDpiForMonitor() from shcored.dll
+
+ shcore.reset(LoadLibraryW(L"Shcore.dll"));
+
+ if (shcore) {
+ // windows 8.1+ only
+ GetDpiForMonitor = reinterpret_cast<GetDpiForMonitorFunction*>(
+ GetProcAddress(shcore.get(), "GetDpiForMonitor"));
+ }
+
+ checked = true;
+ }
+
+ if (!GetDpiForMonitor) {
+ // get the desktop dpi instead
+ return getDesktopDpi();
+ }
+
+
+ // there's no way to get an HMONITOR from a device name, so all monitors
+ // will have to be enumerated and their name checked
+ HMONITOR hm = findMonitor(monitorDevice);
+ if (!hm) {
+ log::error("can't get dpi for monitor '{}', not found", monitorDevice);
+ return 0;
+ }
+
+ UINT dpiX=0, dpiY=0;
+ const auto r = GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+
+ if (FAILED(r)) {
+ log::error(
+ "GetDpiForMonitor() failed for '{}', {}",
+ monitorDevice, formatSystemMessage(r));
+
+ return 0;
+ }
+
+ // dpiX and dpiY are always identical, as per the documentation
+ return dpiX;
+}
+
+
+Display::Display(QString adapter, QString monitorDevice, bool primary) :
+ m_adapter(std::move(adapter)),
+ m_monitorDevice(std::move(monitorDevice)),
+ m_primary(primary),
+ m_resX(0), m_resY(0), m_dpi(0), m_refreshRate(0)
+{
+ getSettings();
+ m_dpi = getDpi(m_monitorDevice);
+}
+
+const QString& Display::adapter() const
+{
+ return m_adapter;
+}
+
+const QString& Display::monitorDevice() const
+{
+ return m_monitorDevice;
+}
+
+bool Display::primary()
+{
+ return m_primary;
+}
+
+int Display::resX() const
+{
+ return m_resX;
+}
+
+int Display::resY() const
+{
+ return m_resY;
+}
+
+int Display::dpi()
+{
+ return m_dpi;
+}
+
+int Display::refreshRate() const
+{
+ return m_refreshRate;
+}
+
+QString Display::toString() const
+{
+ return QString("%1*%2 %3hz dpi=%4 on %5%6")
+ .arg(m_resX)
+ .arg(m_resY)
+ .arg(m_refreshRate)
+ .arg(m_dpi)
+ .arg(m_adapter)
+ .arg(m_primary ? " (primary)" : "");
+}
+
+void Display::getSettings()
+{
+ DEVMODEW dm = {};
+ dm.dmSize = sizeof(dm);
+
+ const auto wsDevice = m_monitorDevice.toStdWString();
+
+ if (!EnumDisplaySettingsW(wsDevice.c_str(), ENUM_CURRENT_SETTINGS, &dm)) {
+ log::error("EnumDisplaySettings() failed for '{}'", m_monitorDevice);
+ return;
+ }
+
+ // all these fields should be available
+
+ if (dm.dmFields & DM_DISPLAYFREQUENCY) {
+ m_refreshRate = dm.dmDisplayFrequency;
+ }
+
+ if (dm.dmFields & DM_PELSWIDTH) {
+ m_resX = dm.dmPelsWidth;
+ }
+
+ if (dm.dmFields & DM_PELSHEIGHT) {
+ m_resY = dm.dmPelsHeight;
+ }
+}
+
+
+Metrics::Metrics()
+{
+ getDisplays();
+}
+
+const std::vector<Display>& Metrics::displays() const
+{
+ return m_displays;
+}
+
+void Metrics::getDisplays()
+{
+ // don't bother if it goes over 100
+ for (int i=0; i<100; ++i) {
+ DISPLAY_DEVICEW device = {};
+ device.cb = sizeof(device);
+
+ if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) {
+ // no more
+ break;
+ }
+
+ // EnumDisplayDevices() seems to be returning a lot of devices that are
+ // not actually monitors, but those don't have the
+ // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set
+ if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) {
+ continue;
+ }
+
+ m_displays.emplace_back(
+ QString::fromWCharArray(device.DeviceString),
+ QString::fromWCharArray(device.DeviceName),
+ (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE));
+ }
+}
+
+} // namespace
diff --git a/src/envmetrics.h b/src/envmetrics.h
new file mode 100644
index 00000000..bede36fc
--- /dev/null
+++ b/src/envmetrics.h
@@ -0,0 +1,72 @@
+#include <QString>
+#include <vector>
+
+namespace env
+{
+
+// information about a monitor
+//
+class Display
+{
+public:
+ Display(QString adapter, QString monitorDevice, bool primary);
+
+ // display name of the adapter running the monitor
+ //
+ const QString& adapter() const;
+
+ // internal device name of the monitor, this is not a display name
+ //
+ const QString& monitorDevice() const;
+
+ // whether this monitor is the primary
+ //
+ bool primary();
+
+ // resolution
+ //
+ int resX() const;
+ int resY() const;
+
+ // dpi
+ //
+ int dpi();
+
+ // refresh rate in hz
+ //
+ int refreshRate() const;
+
+ // string representation
+ //
+ QString toString() const;
+
+private:
+ QString m_adapter;
+ QString m_monitorDevice;
+ bool m_primary;
+ int m_resX, m_resY;
+ int m_dpi;
+ int m_refreshRate;
+
+ void getSettings();
+};
+
+
+// holds various information about Windows metrics
+//
+class Metrics
+{
+public:
+ Metrics();
+
+ // list of displays on the system
+ //
+ const std::vector<Display>& displays() const;
+
+private:
+ std::vector<Display> m_displays;
+
+ void getDisplays();
+};
+
+} // namespace
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
new file mode 100644
index 00000000..8cea414a
--- /dev/null
+++ b/src/envmodule.cpp
@@ -0,0 +1,376 @@
+#include "envmodule.h"
+#include "env.h"
+#include <utility.h>
+#include <log.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+Module::Module(QString path, std::size_t fileSize)
+ : m_path(std::move(path)), m_fileSize(fileSize)
+{
+ const auto fi = getFileInfo();
+
+ m_version = getVersion(fi.ffi);
+ m_timestamp = getTimestamp(fi.ffi);
+ m_versionString = fi.fileDescription;
+ m_md5 = getMD5();
+}
+
+const QString& Module::path() const
+{
+ return m_path;
+}
+
+QString Module::displayPath() const
+{
+ return QDir::fromNativeSeparators(m_path.toLower());
+}
+
+std::size_t Module::fileSize() const
+{
+ return m_fileSize;
+}
+
+const QString& Module::version() const
+{
+ return m_version;
+}
+
+const QString& Module::versionString() const
+{
+ return m_versionString;
+}
+
+const QDateTime& Module::timestamp() const
+{
+ return m_timestamp;
+}
+
+const QString& Module::md5() const
+{
+ return m_md5;
+}
+
+QString Module::timestampString() const
+{
+ if (!m_timestamp.isValid()) {
+ return "(no timestamp)";
+ }
+
+ return m_timestamp.toString(Qt::DateFormat::ISODate);
+}
+
+QString Module::toString() const
+{
+ QStringList sl;
+
+ // file size
+ sl.push_back(displayPath());
+ sl.push_back(QString("%1 B").arg(m_fileSize));
+
+ // version
+ if (m_version.isEmpty() && m_versionString.isEmpty()) {
+ sl.push_back("(no version)");
+ } else {
+ if (!m_version.isEmpty()) {
+ sl.push_back(m_version);
+ }
+
+ if (!m_versionString.isEmpty() && m_versionString != m_version) {
+ sl.push_back(versionString());
+ }
+ }
+
+ // timestamp
+ if (m_timestamp.isValid()) {
+ sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate));
+ } else {
+ sl.push_back("(no timestamp)");
+ }
+
+ // md5
+ if (!m_md5.isEmpty()) {
+ sl.push_back(m_md5);
+ }
+
+ return sl.join(", ");
+}
+
+Module::FileInfo Module::getFileInfo() const
+{
+ const auto wspath = m_path.toStdWString();
+
+ // getting version info size
+ DWORD dummy = 0;
+ const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy);
+
+ if (size == 0) {
+ const auto e = GetLastError();
+
+ if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) {
+ // not an error, no version information built into that module
+ return {};
+ }
+
+ log::error(
+ "GetFileVersionInfoSizeW() failed on '{}', {}",
+ m_path, formatSystemMessage(e));
+
+ return {};
+ }
+
+ // getting version info
+ auto buffer = std::make_unique<std::byte[]>(size);
+
+ if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) {
+ const auto e = GetLastError();
+
+ log::error(
+ "GetFileVersionInfoW() failed on '{}', {}",
+ m_path, formatSystemMessage(e));
+
+ return {};
+ }
+
+ // the version info has two major parts: a fixed version and a localizable
+ // set of strings
+
+ FileInfo fi;
+ fi.ffi = getFixedFileInfo(buffer.get());
+ fi.fileDescription = getFileDescription(buffer.get());
+
+ return fi;
+}
+
+VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const
+{
+ void* valuePointer = nullptr;
+ unsigned int valueSize = 0;
+
+ // the fixed version info is in the root
+ const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize);
+
+ if (!ret || !valuePointer || valueSize == 0) {
+ // not an error, no fixed file info
+ return {};
+ }
+
+ const auto* fi = static_cast<VS_FIXEDFILEINFO*>(valuePointer);
+
+ // signature is always 0xfeef04bd
+ if (fi->dwSignature != 0xfeef04bd) {
+ log::error(
+ "bad file info signature {:#x} for '{}'",
+ fi->dwSignature, m_path);
+
+ return {};
+ }
+
+ return *fi;
+}
+
+QString Module::getFileDescription(std::byte* buffer) const
+{
+ struct LANGANDCODEPAGE
+ {
+ WORD wLanguage;
+ WORD wCodePage;
+ };
+
+ void* valuePointer = nullptr;
+ unsigned int valueSize = 0;
+
+ // getting list of available languages
+ auto ret = VerQueryValueW(
+ buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize);
+
+ if (!ret || !valuePointer || valueSize == 0) {
+ log::error("VerQueryValueW() for translations failed on '{}'", m_path);
+ return {};
+ }
+
+ // number of languages
+ const auto count = valueSize / sizeof(LANGANDCODEPAGE);
+ if (count == 0) {
+ return {};
+ }
+
+ // using the first language in the list to get FileVersion
+ const auto* lcp = static_cast<LANGANDCODEPAGE*>(valuePointer);
+
+ const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion")
+ .arg(lcp->wLanguage, 4, 16, QChar('0'))
+ .arg(lcp->wCodePage, 4, 16, QChar('0'));
+
+ ret = VerQueryValueW(
+ buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize);
+
+ if (!ret || !valuePointer || valueSize == 0) {
+ // not an error, no file version
+ return {};
+ }
+
+ // valueSize includes the null terminator
+ return QString::fromWCharArray(
+ static_cast<wchar_t*>(valuePointer), valueSize - 1);
+}
+
+QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const
+{
+ if (fi.dwSignature == 0) {
+ return {};
+ }
+
+ const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff;
+ const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff;
+ const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff;
+ const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff;
+
+ if (major == 0 && minor == 0 && maintenance == 0 && build == 0) {
+ return {};
+ }
+
+ return QString("%1.%2.%3.%4")
+ .arg(major).arg(minor).arg(maintenance).arg(build);
+}
+
+QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const
+{
+ FILETIME ft = {};
+
+ if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) {
+ // if the file info is invalid or doesn't have a date, use the creation
+ // time on the file
+
+ // opening the file
+ HandlePtr h(CreateFileW(
+ m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
+
+ if (h.get() == INVALID_HANDLE_VALUE) {
+ const auto e = GetLastError();
+
+ log::error(
+ "can't open file '{}' for timestamp, {}",
+ m_path, formatSystemMessage(e));
+
+ return {};
+ }
+
+ // getting the file time
+ if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "can't get file time for '{}', {}",
+ m_path, formatSystemMessage(e));
+
+ return {};
+ }
+ } else {
+ // use the time from the file info
+ ft.dwHighDateTime = fi.dwFileDateMS;
+ ft.dwLowDateTime = fi.dwFileDateLS;
+ }
+
+
+ // converting to SYSTEMTIME
+ SYSTEMTIME utc = {};
+
+ if (!FileTimeToSystemTime(&ft, &utc)) {
+ log::error(
+ "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'",
+ ft.dwHighDateTime, ft.dwLowDateTime, m_path);
+
+ return {};
+ }
+
+ return QDateTime(
+ QDate(utc.wYear, utc.wMonth, utc.wDay),
+ QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds));
+}
+
+QString Module::getMD5() const
+{
+ if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) {
+ // don't calculate md5 for system files, it's not really relevant and
+ // it takes a while
+ return {};
+ }
+
+ // opening the file
+ QFile f(m_path);
+
+ if (!f.open(QFile::ReadOnly)) {
+ log::error("failed to open file '{}' for md5", m_path);
+ return {};
+ }
+
+ // hashing
+ QCryptographicHash hash(QCryptographicHash::Md5);
+ if (!hash.addData(&f)) {
+ log::error("failed to calculate md5 for '{}'", m_path);
+ return {};
+ }
+
+ return hash.result().toHex();
+}
+
+
+std::vector<Module> getLoadedModules()
+{
+ HandlePtr snapshot(CreateToolhelp32Snapshot(
+ TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId()));
+
+ if (snapshot.get() == INVALID_HANDLE_VALUE)
+ {
+ const auto e = GetLastError();
+ log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ MODULEENTRY32 me = {};
+ me.dwSize = sizeof(me);
+
+ // first module, this shouldn't fail because there's at least the executable
+ if (!Module32First(snapshot.get(), &me))
+ {
+ const auto e = GetLastError();
+ log::error("Module32First() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ std::vector<Module> v;
+
+ for (;;)
+ {
+ const auto path = QString::fromWCharArray(me.szExePath);
+ if (!path.isEmpty()) {
+ v.push_back(Module(path, me.modBaseSize));
+ }
+
+ // next module
+ if (!Module32Next(snapshot.get(), &me)) {
+ const auto e = GetLastError();
+
+ // no more modules is not an error
+ if (e != ERROR_NO_MORE_FILES) {
+ log::error("Module32Next() failed, {}", formatSystemMessage(e));
+ }
+
+ break;
+ }
+ }
+
+ // sorting by display name
+ std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) {
+ return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0);
+ });
+
+ return v;
+}
+
+} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
new file mode 100644
index 00000000..ea1156bd
--- /dev/null
+++ b/src/envmodule.h
@@ -0,0 +1,98 @@
+#include <QString>
+#include <QDateTime>
+
+namespace env
+{
+
+// represents one module
+//
+class Module
+{
+public:
+ explicit Module(QString path, std::size_t fileSize);
+
+ // returns the module's path
+ //
+ const QString& path() const;
+
+ // returns the module's path in lowercase and using forward slashes
+ //
+ QString displayPath() const;
+
+ // returns the size in bytes, may be 0
+ //
+ std::size_t fileSize() const;
+
+ // returns the x.x.x.x version embedded from the version info, may be empty
+ //
+ const QString& version() const;
+
+ // returns the FileVersion entry from the resource file, returns
+ // "(no version)" if not available
+ //
+ const QString& versionString() const;
+
+ // returns the build date from the version info, or the creation time of the
+ // file on the filesystem, may be empty
+ //
+ const QDateTime& timestamp() const;
+
+ // returns the md5 of the file, may be empty for system files
+ //
+ const QString& md5() const;
+
+ // converts timestamp() to a string for display, returns "(no timestamp)" if
+ // not available
+ //
+ QString timestampString() const;
+
+ // returns a string with all the above information on one line
+ //
+ QString toString() const;
+
+private:
+ // contains the information from the version resource
+ //
+ struct FileInfo
+ {
+ VS_FIXEDFILEINFO ffi;
+ QString fileDescription;
+ };
+
+ QString m_path;
+ std::size_t m_fileSize;
+ QString m_version;
+ QDateTime m_timestamp;
+ QString m_versionString;
+ QString m_md5;
+
+ // returns information from the version resource
+ //
+ FileInfo getFileInfo() const;
+
+ // uses VS_FIXEDFILEINFO to build the version string
+ //
+ QString getVersion(const VS_FIXEDFILEINFO& fi) const;
+
+ // uses the file date from VS_FIXEDFILEINFO if available, or gets the
+ // creation date on the file
+ //
+ QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const;
+
+ // returns the md5 hash unless the path contains "\windows\"
+ //
+ QString getMD5() const;
+
+ // gets VS_FIXEDFILEINFO from the file version info buffer
+ //
+ VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const;
+
+ // gets FileVersion from the file version info buffer
+ //
+ QString getFileDescription(std::byte* buffer) const;
+};
+
+
+std::vector<Module> getLoadedModules();
+
+} // namespace env
diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp
new file mode 100644
index 00000000..376be4df
--- /dev/null
+++ b/src/envsecurity.cpp
@@ -0,0 +1,400 @@
+#include "envsecurity.h"
+#include "env.h"
+#include <utility.h>
+#include <log.h>
+
+#include <Wbemidl.h>
+#include <wscapi.h>
+#include <comdef.h>
+#include <netfw.h>
+#pragma comment(lib, "Wbemuuid.lib")
+
+namespace env
+{
+
+using namespace MOBase;
+
+class WMI
+{
+public:
+ class failed {};
+
+ WMI(const std::string& ns)
+ {
+ try
+ {
+ createLocator();
+ createService(ns);
+ setSecurity();
+ }
+ catch(failed&)
+ {
+ }
+ }
+
+ template <class F>
+ void query(const std::string& q, F&& f)
+ {
+ if (!m_locator || !m_service) {
+ return;
+ }
+
+ auto enumerator = getEnumerator(q);
+ if (!enumerator) {
+ return;
+ }
+
+ for (;;)
+ {
+ COMPtr<IWbemClassObject> object;
+
+ {
+ IWbemClassObject* rawObject = nullptr;
+ ULONG count = 0;
+ auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count);
+
+ if (count == 0 || !rawObject) {
+ break;
+ }
+
+ if (FAILED(ret)) {
+ log::error("enum->next() failed, {}", formatSystemMessage(ret));
+ break;
+ }
+
+ object.reset(rawObject);
+ }
+
+ f(object.get());
+ }
+ }
+
+private:
+ COMPtr<IWbemLocator> m_locator;
+ COMPtr<IWbemServices> m_service;
+
+ void createLocator()
+ {
+ void* rawLocator = nullptr;
+
+ const auto ret = CoCreateInstance(
+ CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IWbemLocator, &rawLocator);
+
+ if (FAILED(ret) || !rawLocator) {
+ log::error(
+ "CoCreateInstance for WbemLocator failed, {}",
+ formatSystemMessage(ret));
+
+ throw failed();
+ }
+
+ m_locator.reset(static_cast<IWbemLocator*>(rawLocator));
+ }
+
+ void createService(const std::string& ns)
+ {
+ IWbemServices* rawService = nullptr;
+
+ const auto res = m_locator->ConnectServer(
+ _bstr_t(ns.c_str()),
+ nullptr, nullptr, nullptr, 0, nullptr, nullptr,
+ &rawService);
+
+ if (FAILED(res) || !rawService) {
+ log::error(
+ "locator->ConnectServer() failed for namespace '{}', {}",
+ ns, formatSystemMessage(res));
+
+ throw failed();
+ }
+
+ m_service.reset(rawService);
+ }
+
+ void setSecurity()
+ {
+ auto ret = CoSetProxyBlanket(
+ m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
+ RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE);
+
+ if (FAILED(ret))
+ {
+ log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret));
+ throw failed();
+ }
+ }
+
+ COMPtr<IEnumWbemClassObject> getEnumerator(
+ const std::string& query)
+ {
+ IEnumWbemClassObject* rawEnumerator = NULL;
+
+ auto ret = m_service->ExecQuery(
+ bstr_t("WQL"),
+ bstr_t(query.c_str()),
+ WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
+ NULL,
+ &rawEnumerator);
+
+ if (FAILED(ret) || !rawEnumerator)
+ {
+ log::error("query '{}' failed, {}", query, formatSystemMessage(ret));
+ return {};
+ }
+
+ return COMPtr<IEnumWbemClassObject>(rawEnumerator);
+ }
+};
+
+
+SecurityProduct::SecurityProduct(
+ QUuid guid, QString name, int provider,
+ bool active, bool upToDate) :
+ m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider),
+ m_active(active), m_upToDate(upToDate)
+{
+}
+
+const QString& SecurityProduct::name() const
+{
+ return m_name;
+}
+
+int SecurityProduct::provider() const
+{
+ return m_provider;
+}
+
+bool SecurityProduct::active() const
+{
+ return m_active;
+}
+
+bool SecurityProduct::upToDate() const
+{
+ return m_upToDate;
+}
+
+QString SecurityProduct::toString() const
+{
+ QString s;
+
+ s += m_name + " (" + providerToString() + ")";
+
+ if (!m_active) {
+ s += ", inactive";
+ }
+
+ if (!m_upToDate) {
+ s += ", definitions outdated";
+ }
+
+ if (!m_guid.isNull()) {
+ s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces);
+ }
+
+ return s;
+}
+
+QString SecurityProduct::providerToString() const
+{
+ QStringList ps;
+
+ if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) {
+ ps.push_back("firewall");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) {
+ ps.push_back("autoupdate");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) {
+ ps.push_back("antivirus");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) {
+ ps.push_back("antispyware");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) {
+ ps.push_back("settings");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) {
+ ps.push_back("uac");
+ }
+
+ if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) {
+ ps.push_back("service");
+ }
+
+ if (ps.empty()) {
+ return "doesn't provider anything";
+ }
+
+ return ps.join("|");
+}
+
+
+std::vector<SecurityProduct> getSecurityProductsFromWMI()
+{
+ // some products may be present in multiple queries, such as a product marked
+ // as both antivirus and antispyware, but they'll have the same GUID, so use
+ // that to avoid duplicating entries
+ std::map<QUuid, SecurityProduct> map;
+
+ auto handleProduct = [&](auto* o) {
+ VARIANT prop;
+
+ // display name
+ auto ret = o->Get(L"displayName", 0, &prop, 0, 0);
+ if (FAILED(ret)) {
+ log::error("failed to get displayName, {}", formatSystemMessage(ret));
+ return;
+ }
+
+ if (prop.vt != VT_BSTR) {
+ log::error("displayName is a {}, not a bstr", prop.vt);
+ return;
+ }
+
+ const std::wstring name = prop.bstrVal;
+ VariantClear(&prop);
+
+ // product state
+ ret = o->Get(L"productState", 0, &prop, 0, 0);
+ if (FAILED(ret)) {
+ log::error("failed to get productState, {}", formatSystemMessage(ret));
+ return;
+ }
+
+ if (prop.vt != VT_UI4 && prop.vt != VT_I4) {
+ log::error("productState is a {}, is not a VT_UI4", prop.vt);
+ return;
+ }
+
+ DWORD state = 0;
+ if (prop.vt == VT_I4) {
+ state = prop.lVal;
+ } else {
+ state = prop.ulVal;
+ }
+
+ VariantClear(&prop);
+
+ // guid
+ ret = o->Get(L"instanceGuid", 0, &prop, 0, 0);
+ if (FAILED(ret)) {
+ log::error("failed to get instanceGuid, {}", formatSystemMessage(ret));
+ return;
+ }
+
+ if (prop.vt != VT_BSTR) {
+ log::error("instanceGuid is a {}, is not a bstr", prop.vt);
+ return;
+ }
+
+ const QUuid guid(QString::fromWCharArray(prop.bstrVal));
+ VariantClear(&prop);
+
+ const auto provider = static_cast<int>((state >> 16) & 0xff);
+ const auto scanner = (state >> 8) & 0xff;
+ const auto definitions = state & 0xff;
+
+ const bool active = ((scanner & 0x10) != 0);
+ const bool upToDate = (definitions == 0);
+
+ map.insert({
+ guid,
+ {guid, QString::fromStdWString(name), provider, active, upToDate}});
+ };
+
+ {
+ WMI wmi("root\\SecurityCenter2");
+ wmi.query("select * from AntivirusProduct", handleProduct);
+ wmi.query("select * from FirewallProduct", handleProduct);
+ wmi.query("select * from AntiSpywareProduct", handleProduct);
+ }
+
+ {
+ WMI wmi("root\\SecurityCenter");
+ wmi.query("select * from AntivirusProduct", handleProduct);
+ wmi.query("select * from FirewallProduct", handleProduct);
+ wmi.query("select * from AntiSpywareProduct", handleProduct);
+ }
+
+ std::vector<SecurityProduct> v;
+
+ for (auto&& p : map) {
+ v.push_back(p.second);
+ }
+
+ return v;
+}
+
+std::optional<SecurityProduct> getWindowsFirewall()
+{
+ HRESULT hr = 0;
+
+ COMPtr<INetFwPolicy2> policy;
+
+ {
+ void* rawPolicy = nullptr;
+
+ hr = CoCreateInstance(
+ __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER,
+ __uuidof(INetFwPolicy2), &rawPolicy);
+
+ if (FAILED(hr) || !rawPolicy) {
+ log::error(
+ "CoCreateInstance for NetFwPolicy2 failed, {}",
+ formatSystemMessage(hr));
+
+ return {};
+ }
+
+ policy.reset(static_cast<INetFwPolicy2*>(rawPolicy));
+ }
+
+ VARIANT_BOOL enabledVariant;
+
+ if (policy) {
+ hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant);
+ if (FAILED(hr))
+ {
+ log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr));
+ return {};
+ }
+ }
+
+ const auto enabled = (enabledVariant != VARIANT_FALSE);
+ if (!enabled) {
+ return {};
+ }
+
+ return SecurityProduct(
+ {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true);
+}
+
+
+std::vector<SecurityProduct> getSecurityProducts()
+{
+ std::vector<SecurityProduct> v;
+
+ {
+ auto fromWMI = getSecurityProductsFromWMI();
+ v.insert(
+ v.end(),
+ std::make_move_iterator(fromWMI.begin()),
+ std::make_move_iterator(fromWMI.end()));
+ }
+
+ if (auto p=getWindowsFirewall()) {
+ v.push_back(std::move(*p));
+ }
+
+ return v;
+}
+
+} // namespace
diff --git a/src/envsecurity.h b/src/envsecurity.h
new file mode 100644
index 00000000..200cb531
--- /dev/null
+++ b/src/envsecurity.h
@@ -0,0 +1,49 @@
+#include <QUuid>
+#include <QString>
+
+namespace env
+{
+
+// represents a security product, such as an antivirus or a firewall
+//
+class SecurityProduct
+{
+public:
+ SecurityProduct(
+ QUuid guid, QString name, int provider,
+ bool active, bool upToDate);
+
+ // display name of the product
+ //
+ const QString& name() const;
+
+ // a bunch of _WSC_SECURITY_PROVIDER flags
+ //
+ int provider() const;
+
+ // whether the product is active
+ //
+ bool active() const;
+
+ // whether its definitions are up-to-date
+ //
+ bool upToDate() const;
+
+ // string representation of the above
+ //
+ QString toString() const;
+
+private:
+ QUuid m_guid;
+ QString m_name;
+ int m_provider;
+ bool m_active;
+ bool m_upToDate;
+
+ QString providerToString() const;
+};
+
+
+std::vector<SecurityProduct> getSecurityProducts();
+
+} // namespace env
diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp
new file mode 100644
index 00000000..99495c39
--- /dev/null
+++ b/src/envshortcut.cpp
@@ -0,0 +1,374 @@
+#include "envshortcut.h"
+#include "env.h"
+#include "executableslist.h"
+#include "instancemanager.h"
+#include <utility.h>
+#include <log.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+class ShellLinkException
+{
+public:
+ ShellLinkException(QString s)
+ : m_what(std::move(s))
+ {
+ }
+
+ const QString& what() const
+ {
+ return m_what;
+ }
+
+private:
+ QString m_what;
+};
+
+// just a wrapper around IShellLink operations that throws ShellLinkException
+// on errors
+//
+class ShellLinkWrapper
+{
+public:
+ ShellLinkWrapper()
+ {
+ m_link = createShellLink();
+ m_file = createPersistFile();
+ }
+
+ void setPath(const QString& s)
+ {
+ if (s.isEmpty()) {
+ throw ShellLinkException("path cannot be empty");
+ }
+
+ const auto r = m_link->SetPath(s.toStdWString().c_str());
+ throwOnFail(r, QString("failed to set target path '%1'").arg(s));
+ }
+
+ void setArguments(const QString& s)
+ {
+ const auto r = m_link->SetArguments(s.toStdWString().c_str());
+ throwOnFail(r, QString("failed to set arguments '%1'").arg(s));
+ }
+
+ void setDescription(const QString& s)
+ {
+ if (s.isEmpty()) {
+ return;
+ }
+
+ const auto r = m_link->SetDescription(s.toStdWString().c_str());
+ throwOnFail(r, QString("failed to set description '%1'").arg(s));
+ }
+
+ void setIcon(const QString& file, int i)
+ {
+ if (file.isEmpty()) {
+ return;
+ }
+
+ const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i);
+ throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i));
+ }
+
+ void setWorkingDirectory(const QString& s)
+ {
+ if (s.isEmpty()) {
+ return;
+ }
+
+ const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str());
+ throwOnFail(r, QString("failed to set working directory '%1'").arg(s));
+ }
+
+ void save(const QString& path)
+ {
+ const auto r = m_file->Save(path.toStdWString().c_str(), TRUE);
+ throwOnFail(r, QString("failed to save link '%1'").arg(path));
+ }
+
+private:
+ COMPtr<IShellLink> m_link;
+ COMPtr<IPersistFile> m_file;
+
+ void throwOnFail(HRESULT r, const QString& s)
+ {
+ if (FAILED(r)) {
+ throw ShellLinkException(QString("%1, %2")
+ .arg(s)
+ .arg(formatSystemMessage(r)));
+ }
+ }
+
+ COMPtr<IShellLink> createShellLink()
+ {
+ void* link = nullptr;
+
+ const auto r = CoCreateInstance(
+ CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLink, &link);
+
+ throwOnFail(r, "failed to create IShellLink instance");
+
+ if (!link) {
+ throw ShellLinkException("creating IShellLink worked, pointer is null");
+ }
+
+ return COMPtr<IShellLink>(static_cast<IShellLink*>(link));
+ }
+
+ COMPtr<IPersistFile> createPersistFile()
+ {
+ void* file = nullptr;
+
+ const auto r = m_link->QueryInterface(IID_IPersistFile, &file);
+ throwOnFail(r, "failed to get IPersistFile interface");
+
+ if (!file) {
+ throw ShellLinkException("querying IPersistFile worked, pointer is null");
+ }
+
+ return COMPtr<IPersistFile>(static_cast<IPersistFile*>(file));
+ }
+};
+
+
+Shortcut::Shortcut()
+ : m_iconIndex(0)
+{
+}
+
+Shortcut::Shortcut(const Executable& exe)
+ : Shortcut()
+{
+ m_name = exe.title();
+ m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
+
+ m_arguments = QString("\"moshortcut://%1:%2\"")
+ .arg(InstanceManager::instance().currentInstance())
+ .arg(exe.title());
+
+ m_description = QString("Run %1 with ModOrganizer").arg(exe.title());
+
+ if (exe.usesOwnIcon()) {
+ m_icon = exe.binaryInfo().absoluteFilePath();
+ }
+
+ m_workingDirectory = qApp->applicationDirPath();
+}
+
+Shortcut& Shortcut::name(const QString& s)
+{
+ m_name = s;
+ return *this;
+}
+
+Shortcut& Shortcut::target(const QString& s)
+{
+ m_target = s;
+ return *this;
+}
+
+Shortcut& Shortcut::arguments(const QString& s)
+{
+ m_arguments = s;
+ return *this;
+}
+
+Shortcut& Shortcut::description(const QString& s)
+{
+ m_description = s;
+ return *this;
+}
+
+Shortcut& Shortcut::icon(const QString& s, int index)
+{
+ m_icon = s;
+ m_iconIndex = index;
+ return *this;
+}
+
+Shortcut& Shortcut::workingDirectory(const QString& s)
+{
+ m_workingDirectory = s;
+ return *this;
+}
+
+bool Shortcut::exists(Locations loc) const
+{
+ const auto path = shortcutPath(loc);
+ if (path.isEmpty()) {
+ return false;
+ }
+
+ return QFileInfo(path).exists();
+}
+
+bool Shortcut::toggle(Locations loc)
+{
+ if (exists(loc)) {
+ return remove(loc);
+ } else {
+ return add(loc);
+ }
+}
+
+bool Shortcut::add(Locations loc)
+{
+ log::debug(
+ "adding shortcut to {}:\n"
+ " . name: '{}'\n"
+ " . target: '{}'\n"
+ " . arguments: '{}'\n"
+ " . description: '{}'\n"
+ " . icon: '{}' @ {}\n"
+ " . working directory: '{}'",
+ toString(loc),
+ m_name,
+ m_target,
+ m_arguments,
+ m_description,
+ m_icon, m_iconIndex,
+ m_workingDirectory);
+
+ if (m_target.isEmpty()) {
+ log::error("shortcut: target is empty");
+ return false;
+ }
+
+ const auto path = shortcutPath(loc);
+ if (path.isEmpty()) {
+ return false;
+ }
+
+ log::debug("shorcut file will be saved at '{}'", path);
+
+ try
+ {
+ ShellLinkWrapper link;
+
+ link.setPath(m_target);
+ link.setArguments(m_arguments);
+ link.setDescription(m_description);
+ link.setIcon(m_icon, m_iconIndex);
+ link.setWorkingDirectory(m_workingDirectory);
+
+ link.save(path);
+
+ return true;
+ }
+ catch(ShellLinkException& e)
+ {
+ log::error("{}\nshortcut file was not saved", e.what());
+ }
+
+ return false;
+}
+
+bool Shortcut::remove(Locations loc)
+{
+ log::debug("removing shortcut for '{}' from {}", m_name, toString(loc));
+
+ const auto path = shortcutPath(loc);
+ if (path.isEmpty()) {
+ return false;
+ }
+
+ log::debug("path to shortcut file is '{}'", path);
+
+ if (!QFile::exists(path)) {
+ log::error("can't remove shortcut '{}', file not found", path);
+ return false;
+ }
+
+ if (!MOBase::shellDelete({path})) {
+ const auto e = ::GetLastError();
+
+ log::error(
+ "failed to remove shortcut '{}', {}",
+ path, formatSystemMessage(e));
+
+ return false;
+ }
+
+ return true;
+}
+
+QString Shortcut::shortcutPath(Locations loc) const
+{
+ const auto dir = shortcutDirectory(loc);
+ if (dir.isEmpty()) {
+ return {};
+ }
+
+ const auto file = shortcutFilename();
+ if (file.isEmpty()) {
+ return {};
+ }
+
+ return dir + QDir::separator() + file;
+}
+
+QString Shortcut::shortcutDirectory(Locations loc) const
+{
+ QString dir;
+
+ try
+ {
+ switch (loc)
+ {
+ case Desktop:
+ dir = MOBase::getDesktopDirectory();
+ break;
+
+ case StartMenu:
+ dir = MOBase::getStartMenuDirectory();
+ break;
+
+ case None:
+ default:
+ log::error("shortcut: bad location {}", loc);
+ break;
+ }
+ }
+ catch(std::exception&)
+ {
+ }
+
+ return QDir::toNativeSeparators(dir);
+}
+
+QString Shortcut::shortcutFilename() const
+{
+ if (m_name.isEmpty()) {
+ log::error("shortcut name is empty");
+ return {};
+ }
+
+ return m_name + ".lnk";
+}
+
+
+QString toString(Shortcut::Locations loc)
+{
+ switch (loc)
+ {
+ case Shortcut::None:
+ return "none";
+
+ case Shortcut::Desktop:
+ return "desktop";
+
+ case Shortcut::StartMenu:
+ return "start menu";
+
+ default:
+ return QString("? (%1)").arg(static_cast<int>(loc));
+ }
+}
+
+} // namespace
diff --git a/src/envshortcut.h b/src/envshortcut.h
new file mode 100644
index 00000000..82eea191
--- /dev/null
+++ b/src/envshortcut.h
@@ -0,0 +1,105 @@
+#include <QString>
+
+class Executable;
+
+namespace env
+{
+
+// an application shortcut that can be either on the desktop or the start menu
+//
+class Shortcut
+{
+public:
+ // location of a shortcut
+ //
+ enum Locations
+ {
+ None = 0,
+
+ // on the desktop
+ Desktop,
+
+ // in the start menu
+ StartMenu
+ };
+
+
+ // empty shortcut
+ //
+ Shortcut();
+
+ // shortcut from an executable
+ //
+ explicit Shortcut(const Executable& exe);
+
+ // sets the name of the shortcut, shown on icons and start menu entries
+ //
+ Shortcut& name(const QString& s);
+
+ // the program to start
+ //
+ Shortcut& target(const QString& s);
+
+ // arguments to pass
+ //
+ Shortcut& arguments(const QString& s);
+
+ // shows in the status bar of explorer, for example
+ //
+ Shortcut& description(const QString& s);
+
+ // path to a binary that contains the icon and its index
+ //
+ Shortcut& icon(const QString& s, int index=0);
+
+ // "start in" option for this shortcut
+ //
+ Shortcut& workingDirectory(const QString& s);
+
+
+ // returns whether this shortcut already exists at the given location; this
+ // does not check whether the shortcut parameters are different, it merely if
+ // the .lnk file exists
+ //
+ bool exists(Locations loc) const;
+
+ // calls remove() if exists(), or add()
+ //
+ bool toggle(Locations loc);
+
+ // adds the shortcut to the given location
+ //
+ bool add(Locations loc);
+
+ // removes the shortcut from the given location
+ //
+ bool remove(Locations loc);
+
+private:
+ QString m_name;
+ QString m_target;
+ QString m_arguments;
+ QString m_description;
+ QString m_icon;
+ int m_iconIndex;
+ QString m_workingDirectory;
+
+ // returns the path where the shortcut file should be saved
+ //
+ QString shortcutPath(Locations loc) const;
+
+ // returns the directory where the shortcut file should be saved
+ //
+ QString shortcutDirectory(Locations loc) const;
+
+ // returns the filename of the shortcut file that should be used when saving
+ //
+ QString shortcutFilename() const;
+};
+
+
+// returns a string representation of the given location
+//
+QString toString(Shortcut::Locations loc);
+
+} // namespace
diff --git a/src/envwindows.cpp b/src/envwindows.cpp
new file mode 100644
index 00000000..3932a9b5
--- /dev/null
+++ b/src/envwindows.cpp
@@ -0,0 +1,237 @@
+#include "envwindows.h"
+#include "env.h"
+#include <utility.h>
+#include <log.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+WindowsInfo::WindowsInfo()
+{
+ // loading ntdll.dll, the functions will be found with GetProcAddress()
+ LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll"));
+
+ if (!ntdll) {
+ log::error("failed to load ntdll.dll while getting version");
+ return;
+ } else {
+ m_reported = getReportedVersion(ntdll.get());
+ m_real = getRealVersion(ntdll.get());
+ }
+
+ m_release = getRelease();
+ m_elevated = getElevated();
+}
+
+bool WindowsInfo::compatibilityMode() const
+{
+ if (m_real == Version()) {
+ // don't know the real version, can't guess compatibility mode
+ return false;
+ }
+
+ return (m_real != m_reported);
+}
+
+const WindowsInfo::Version& WindowsInfo::reportedVersion() const
+{
+ return m_reported;
+}
+
+const WindowsInfo::Version& WindowsInfo::realVersion() const
+{
+ return m_real;
+}
+
+const WindowsInfo::Release& WindowsInfo::release() const
+{
+ return m_release;
+}
+
+std::optional<bool> WindowsInfo::isElevated() const
+{
+ return m_elevated;
+}
+
+QString WindowsInfo::toString() const
+{
+ QStringList sl;
+
+ const QString reported = m_reported.toString();
+ const QString real = m_real.toString();
+
+ // version
+ sl.push_back("version " + reported);
+
+ // real version if different
+ if (compatibilityMode()) {
+ sl.push_back("real version " + real);
+ }
+
+ // build.UBR, such as 17763.557
+ if (m_release.UBR != 0) {
+ DWORD build = 0;
+
+ if (compatibilityMode()) {
+ build = m_real.build;
+ } else {
+ build = m_reported.build;
+ }
+
+ sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR));
+ }
+
+ // release ID
+ if (!m_release.ID.isEmpty()) {
+ sl.push_back("release " + m_release.ID);
+ }
+
+ // buildlab string
+ if (!m_release.buildLab.isEmpty()) {
+ sl.push_back(m_release.buildLab);
+ }
+
+ // product name
+ if (!m_release.productName.isEmpty()) {
+ sl.push_back(m_release.productName);
+ }
+
+ // elevated
+ QString elevated = "?";
+ if (m_elevated.has_value()) {
+ elevated = (*m_elevated ? "yes" : "no");
+ }
+
+ sl.push_back("elevated: " + elevated);
+
+ return sl.join(", ");
+}
+
+WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const
+{
+ // windows has been deprecating pretty much all the functions having to do
+ // with getting version information because apparently, people keep misusing
+ // them for feature detection
+ //
+ // there's still RtlGetVersion() though
+
+ using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW);
+
+ auto* RtlGetVersion = reinterpret_cast<RtlGetVersionType*>(
+ GetProcAddress(ntdll, "RtlGetVersion"));
+
+ if (!RtlGetVersion) {
+ log::error("RtlGetVersion() not found in ntdll.dll");
+ return {};
+ }
+
+ OSVERSIONINFOEX vi = {};
+ vi.dwOSVersionInfoSize = sizeof(vi);
+
+ // this apparently never fails
+ RtlGetVersion((RTL_OSVERSIONINFOW*)&vi);
+
+ return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber};
+}
+
+WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const
+{
+ // getting the actual windows version is more difficult because all the
+ // functions are lying when running in compatibility mode
+ //
+ // RtlGetNtVersionNumbers() is an undocumented function that seems to work
+ // fine, but it might not in the future
+
+ using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*);
+
+ auto* RtlGetNtVersionNumbers = reinterpret_cast<RtlGetNtVersionNumbersType*>(
+ GetProcAddress(ntdll, "RtlGetNtVersionNumbers"));
+
+ if (!RtlGetNtVersionNumbers) {
+ log::error("RtlGetNtVersionNumbers not found in ntdll.dll");
+ return {};
+ }
+
+ DWORD major=0, minor=0, build=0;
+ RtlGetNtVersionNumbers(&major, &minor, &build);
+
+ // for whatever reason, the build number has 0xf0000000 set
+ build = 0x0fffffff & build;
+
+ return {major, minor, build};
+}
+
+WindowsInfo::Release WindowsInfo::getRelease() const
+{
+ // there are several interesting items in the registry, but most of them
+ // are undocumented, not always available, and localizable
+ //
+ // most of them are used to provide as much information as possible in case
+ // any of the other versions fail to work
+
+ QSettings settings(
+ R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
+ QSettings::NativeFormat);
+
+ Release r;
+
+ // buildlab seems to be an internal name from the build system
+ r.buildLab = settings.value("BuildLabEx", "").toString();
+ if (r.buildLab.isEmpty()) {
+ r.buildLab = settings.value("BuildLab", "").toString();
+ if (r.buildLab.isEmpty()) {
+ r.buildLab = settings.value("BuildBranch", "").toString();
+ }
+ }
+
+ // localized name of windows, such as "Windows 10 Pro"
+ r.productName = settings.value("ProductName", "").toString();
+
+ // release ID, such as 1803
+ r.ID = settings.value("ReleaseId", "").toString();
+
+ // some other build number, shown in winver.exe
+ r.UBR = settings.value("UBR", 0).toUInt();
+
+ return r;
+}
+
+std::optional<bool> WindowsInfo::getElevated() const
+{
+ HandlePtr token;
+
+ {
+ HANDLE rawToken = 0;
+
+ if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "while trying to check if process is elevated, "
+ "OpenProcessToken() failed: {}", formatSystemMessage(e));
+
+ return {};
+ }
+
+ token.reset(rawToken);
+ }
+
+ TOKEN_ELEVATION e = {};
+ DWORD size = sizeof(TOKEN_ELEVATION);
+
+ if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "while trying to check if process is elevated, "
+ "GetTokenInformation() failed: {}", formatSystemMessage(e));
+
+ return {};
+ }
+
+ return (e.TokenIsElevated != 0);
+}
+
+} // namespace
diff --git a/src/envwindows.h b/src/envwindows.h
new file mode 100644
index 00000000..c23f99f4
--- /dev/null
+++ b/src/envwindows.h
@@ -0,0 +1,106 @@
+#include <QString>
+#include <optional>
+
+namespace env
+{
+
+// a variety of information on windows
+//
+class WindowsInfo
+{
+public:
+ struct Version
+ {
+ DWORD major=0, minor=0, build=0;
+
+ QString toString() const
+ {
+ return QString("%1.%2.%3").arg(major).arg(minor).arg(build);
+ }
+
+ friend bool operator==(const Version& a, const Version& b)
+ {
+ return
+ a.major == b.major &&
+ a.minor == b.minor &&
+ a.build == b.build;
+ }
+
+ friend bool operator!=(const Version& a, const Version& b)
+ {
+ return !(a == b);
+ }
+ };
+
+ struct Release
+ {
+ // the BuildLab entry from the registry, may be empty
+ QString buildLab;
+
+ // product name such as "Windows 10 Pro", may not be in English, may be
+ // empty
+ QString productName;
+
+ // release ID such as 1809, may be mepty
+ QString ID;
+
+ // some sub-build number, undocumented, may be empty
+ DWORD UBR;
+
+ Release()
+ : UBR(0)
+ {
+ }
+ };
+
+
+ WindowsInfo();
+
+ // tries to guess whether this process is running in compatibility mode
+ //
+ bool compatibilityMode() const;
+
+ // returns the Windows version, may not correspond to the actual version
+ // if the process is running in compatibility mode
+ //
+ const Version& reportedVersion() const;
+
+ // tries to guess the real Windows version that's running, can be empty
+ //
+ const Version& realVersion() const;
+
+ // various information about the current release
+ //
+ const Release& release() const;
+
+ // whether this process is running as administrator, may be empty if the
+ // information is not available
+ std::optional<bool> isElevated() const;
+
+ // returns a string with all the above information on one line
+ //
+ QString toString() const;
+
+private:
+ Version m_reported, m_real;
+ Release m_release;
+ std::optional<bool> m_elevated;
+
+ // uses RtlGetVersion() to get the version number as reported by Windows
+ //
+ Version getReportedVersion(HINSTANCE ntdll) const;
+
+ // uses RtlGetNtVersionNumbers() to get the real version number
+ //
+ Version getRealVersion(HINSTANCE ntdll) const;
+
+ // gets various information from the registry
+ //
+ Release getRelease() const;
+
+ // gets whether the process is elevated
+ //
+ std::optional<bool> getElevated() const;
+};
+
+} // namespace
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index 077d2a93..3f76bb6f 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "iplugingame.h"
#include "utility.h"
+#include <log.h>
#include <QFileInfo>
#include <QDir>
@@ -65,7 +66,7 @@ bool ExecutablesList::empty() const
void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
{
- qDebug("setting up configured executables");
+ log::debug("loading executables");
m_Executables.clear();
@@ -103,6 +104,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
if (needsUpgrade)
upgradeFromCustom(game);
+
+ dump();
}
void ExecutablesList::store(QSettings& settings)
@@ -163,7 +166,7 @@ std::vector<Executable> ExecutablesList::getPluginExecutables(
void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game)
{
- qDebug("resetting plugin executables");
+ log::debug("resetting plugin executables");
Q_ASSERT(game != nullptr);
@@ -240,16 +243,16 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags)
if (flags == MoveExisting) {
const auto newTitle = makeNonConflictingTitle(exe.title());
if (!newTitle) {
- qCritical().nospace()
- << "executable '" << exe.title() << "' was in the way but could "
- << "not be renamed";
+ log::error(
+ "executable '{}' was in the way but could not be renamed",
+ exe.title());
return;
}
- qWarning().nospace()
- << "executable '" << itor->title() << "' was in the way and was "
- << "renamed to '" << *newTitle << "'";
+ log::warn(
+ "executable '{}' was in the way and was renamed to '{}'",
+ itor->title(), *newTitle);
itor->title(*newTitle);
itor = end();
@@ -286,15 +289,13 @@ std::optional<QString> ExecutablesList::makeNonConflictingTitle(
title = prefix + QString(" (%1)").arg(i);
}
- qCritical().nospace()
- << "ran out of executable titles for prefix '" << prefix << "'";
-
+ log::error("ran out of executable titles for prefix '{}'", prefix);
return {};
}
void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game)
{
- qDebug() << "upgrading executables list";
+ log::debug("upgrading executables list");
Q_ASSERT(game != nullptr);
@@ -332,6 +333,31 @@ void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game)
}
}
+void ExecutablesList::dump() const
+{
+ for (const auto& e : m_Executables) {
+ QStringList flags;
+
+ if (e.flags() & Executable::ShowInToolbar) {
+ flags.push_back("toolbar");
+ }
+
+ if (e.flags() & Executable::UseApplicationIcon) {
+ flags.push_back("icon");
+ }
+
+ log::debug(
+ " . executable '{}'\n"
+ " binary: {}\n"
+ " arguments: {}\n"
+ " steam ID: {}\n"
+ " directory: {}\n"
+ " flags: {} ({})",
+ e.title(), e.binaryInfo().absoluteFilePath(), e.arguments(),
+ e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags());
+ }
+}
+
Executable::Executable(QString title)
: m_title(title)
diff --git a/src/executableslist.h b/src/executableslist.h
index 2d1dd28e..eda2034e 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -214,6 +214,10 @@ private:
* called when MO is still using the old custom executables from 2.2.0
**/
void upgradeFromCustom(const MOBase::IPluginGame* game);
+
+ // logs all executables
+ //
+ void dump() const;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags)
diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp
index c5c6782b..a97d7742 100644
--- a/src/filerenamer.cpp
+++ b/src/filerenamer.cpp
@@ -1,13 +1,16 @@
#include "filerenamer.h"
+#include <log.h>
#include <QMessageBox>
#include <QFileInfo>
+using namespace MOBase;
+
FileRenamer::FileRenamer(QWidget* parent, QFlags<RenameFlags> flags)
: m_parent(parent), m_flags(flags)
{
// sanity check for flags
if ((m_flags & (HIDE|UNHIDE)) == 0) {
- qCritical("renameFile() missing hide flag");
+ log::error("renameFile() missing hide flag");
// doesn't really matter, it's just for text
m_flags = HIDE;
}
@@ -15,10 +18,10 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags<RenameFlags> flags)
FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName)
{
- qDebug().nospace() << "renaming " << oldName << " to " << newName;
+ log::debug("renaming {} to {}", oldName, newName);
if (QFileInfo(newName).exists()) {
- qDebug().nospace() << newName << " already exists";
+ log::debug("{} already exists", newName);
// target file already exists, confirm replacement
auto answer = confirmReplace(newName);
@@ -26,24 +29,25 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt
switch (answer) {
case DECISION_SKIP: {
// user wants to skip this file
- qDebug().nospace() << "skipping " << oldName;
+ log::debug("skipping {}", oldName);
return RESULT_SKIP;
}
case DECISION_REPLACE: {
- qDebug().nospace() << "removing " << newName;
+ log::debug("removing {}", newName);
+
// user wants to replace the file, so remove it
if (!QFile(newName).remove()) {
- qWarning().nospace() << "failed to remove " << newName;
+ log::warn("failed to remove '{}'", newName);
// removal failed, warn the user and allow canceling
if (!removeFailed(newName)) {
- qDebug().nospace() << "canceling " << oldName;
+ log::debug("canceling {}", oldName);
// user wants to cancel
return RESULT_CANCEL;
}
// ignore this file and continue on
- qDebug().nospace() << "skipping " << oldName;
+ log::debug("skipping {}", oldName);
return RESULT_SKIP;
}
@@ -53,7 +57,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt
case DECISION_CANCEL: // fall-through
default: {
// user wants to stop
- qDebug().nospace() << "canceling";
+ log::debug("canceling");
return RESULT_CANCEL;
}
}
@@ -62,22 +66,22 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt
// target either didn't exist or was removed correctly
if (!QFile::rename(oldName, newName)) {
- qWarning().nospace() << "failed to rename " << oldName << " to " << newName;
+ log::warn("failed to rename '{}' to '{}'", oldName, newName);
// renaming failed, warn the user and allow canceling
if (!renameFailed(oldName, newName)) {
// user wants to cancel
- qDebug().nospace() << "canceling";
+ log::debug("canceling");
return RESULT_CANCEL;
}
// ignore this file and continue on
- qDebug().nospace() << "skipping " << oldName;
+ log::debug("skipping {}", oldName);
return RESULT_SKIP;
}
// everything worked
- qDebug().nospace() << "successfully renamed " << oldName << " to " << newName;
+ log::debug("successfully renamed {} to {}", oldName, newName);
return RESULT_OK;
}
@@ -85,12 +89,12 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName)
{
if (m_flags & REPLACE_ALL) {
// user wants to silently replace all
- qDebug().nospace() << "user has selected replace all";
+ log::debug("user has selected replace all");
return DECISION_REPLACE;
}
else if (m_flags & REPLACE_NONE) {
// user wants to silently skip all
- qDebug().nospace() << "user has selected replace none";
+ log::debug("user has selected replace none");
return DECISION_SKIP;
}
@@ -114,28 +118,28 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName)
switch (answer) {
case QMessageBox::Yes:
- qDebug().nospace() << "user wants to replace";
+ log::debug("user wants to replace");
return DECISION_REPLACE;
case QMessageBox::No:
- qDebug().nospace() << "user wants to skip";
+ log::debug("user wants to skip");
return DECISION_SKIP;
case QMessageBox::YesToAll:
- qDebug().nospace() << "user wants to replace all";
+ log::debug("user wants to replace all");
// remember the answer
m_flags |= REPLACE_ALL;
return DECISION_REPLACE;
case QMessageBox::NoToAll:
- qDebug().nospace() << "user wants to replace none";
+ log::debug("user wants to replace none");
// remember the answer
m_flags |= REPLACE_NONE;
return DECISION_SKIP;
case QMessageBox::Cancel: // fall-through
default:
- qDebug().nospace() << "user wants to cancel";
+ log::debug("user wants to cancel");
return DECISION_CANCEL;
}
}
@@ -155,12 +159,12 @@ bool FileRenamer::removeFailed(const QString& name)
if (answer == QMessageBox::Cancel) {
// user wants to stop
- qDebug().nospace() << "user wants to cancel";
+ log::debug("user wants to cancel");
return false;
}
// skip this one and continue
- qDebug().nospace() << "user wants to skip";
+ log::debug("user wants to skip");
return true;
}
@@ -179,11 +183,11 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName)
if (answer == QMessageBox::Cancel) {
// user wants to stop
- qDebug().nospace() << "user wants to cancel";
+ log::debug("user wants to cancel");
return false;
}
// skip this one and continue
- qDebug().nospace() << "user wants to skip";
+ log::debug("user wants to skip");
return true;
}
diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp
index 44cbb274..0638add3 100644
--- a/src/filterwidget.cpp
+++ b/src/filterwidget.cpp
@@ -1,5 +1,8 @@
#include "filterwidget.h"
#include "eventfilter.h"
+#include <log.h>
+
+using namespace MOBase;
FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent)
: QSortFilterProxyModel(parent), m_filter(fw)
@@ -80,7 +83,7 @@ QModelIndex FilterWidget::map(const QModelIndex& index)
if (m_proxy) {
return m_proxy->mapToSource(index);
} else {
- qCritical() << "FilterWidget::map() called, but proxy isn't set up";
+ log::error("FilterWidget::map() called, but proxy isn't set up");
return index;
}
}
diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp
index b92838c3..b84f785f 100644
--- a/src/forcedloaddialogwidget.cpp
+++ b/src/forcedloaddialogwidget.cpp
@@ -1,9 +1,8 @@
#include "forcedloaddialogwidget.h"
#include "ui_forcedloaddialogwidget.h"
-
-#include <QFileDialog>
-
#include "executableinfo.h"
+#include <log.h>
+#include <QFileDialog>
using namespace MOBase;
@@ -85,7 +84,7 @@ void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked()
if (fileInfo.exists()) {
ui->libraryPathEdit->setText(filePath);
} else {
- qCritical("%ls does not exist", filePath.toStdWString().c_str());
+ log::error("{} does not exist", filePath);
}
}
}
@@ -102,7 +101,7 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked()
if (fileInfo.exists()) {
ui->processEdit->setText(fileName);
} else {
- qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str());
+ log::error("{} does not exist", fileInfo.filePath());
}
}
}
diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp
index 249dae6f..39038f3c 100644
--- a/src/icondelegate.cpp
+++ b/src/icondelegate.cpp
@@ -18,12 +18,14 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "icondelegate.h"
+#include <log.h>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QPixmapCache>
+using namespace MOBase;
IconDelegate::IconDelegate(QObject *parent)
: QStyledItemDelegate(parent)
@@ -54,7 +56,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
if (!QPixmapCache::find(fullIconId, &icon)) {
icon = QIcon(iconId).pixmap(iconWidth, iconWidth);
if (icon.isNull()) {
- qWarning("failed to load icon %s", qUtf8Printable(iconId));
+ log::warn("failed to load icon {}", iconId);
}
QPixmapCache::insert(fullIconId, icon);
}
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index e443a8f2..89d0079f 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -263,7 +263,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool
targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/');
}
if (targetFile == nullptr) {
- qCritical() << "Failed to find backslash in " << data[i]->getFileName();
+ log::error("Failed to find backslash in {}", data[i]->getFileName());
continue;
} else {
// skip the slash
@@ -398,7 +398,7 @@ bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *nod
for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) ||
(!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) {
- qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData());
+ log::debug("{} on the top level", (*iter)->getData().name.toQString());
return true;
}
}
@@ -424,7 +424,7 @@ DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *da
(currentNode->numNodes() == 1)) {
currentNode = *currentNode->nodesBegin();
} else {
- qDebug("not a simple archive");
+ log::debug("not a simple archive");
return nullptr;
}
}
@@ -527,7 +527,7 @@ bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *me
settingsFile.write(originalSettings);
settingsFile.close();
} else {
- qCritical("failed to restore original settings: %s", qUtf8Printable(metaFilename));
+ log::error("failed to restore original settings: {}", metaFilename);
}
return true;
} else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) {
@@ -576,7 +576,7 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, QString game
QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath();
QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory);
- qDebug("installing to \"%s\"", qUtf8Printable(targetDirectoryNative));
+ log::debug("installing to \"{}\"", targetDirectoryNative);
m_InstallationProgress = new QProgressDialog(m_ParentWidget);
ON_BLOCK_EXIT([this] () {
@@ -693,7 +693,7 @@ void InstallationManager::postInstallCleanup()
QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther);
}
if (!QFile::remove(fileInfo.absoluteFilePath())) {
- qWarning() << "Unable to delete " << fileInfo.absoluteFilePath();
+ log::warn("Unable to delete {}", fileInfo.absoluteFilePath());
}
}
directoriesToRemove.insert(fileInfo.absolutePath());
@@ -764,7 +764,7 @@ bool InstallationManager::install(const QString &fileName,
if ((modID == 0) && (guessedModID != -1)) {
modID = guessedModID;
} else if (modID != guessedModID) {
- qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID);
+ log::debug("passed mod id: {}, guessed id: {}", modID, guessedModID);
}
modName.update(guessedModName, GUESS_GOOD);
@@ -774,7 +774,7 @@ bool InstallationManager::install(const QString &fileName,
if (fileInfo.dir() == QDir(m_DownloadsDirectory)) {
m_CurrentFile = fileInfo.fileName();
}
- qDebug("using mod name \"%s\" (id %d) -> %s", qUtf8Printable(modName), modID, qUtf8Printable(m_CurrentFile));
+ log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, m_CurrentFile);
//If there's an archive already open, close it. This happens with the bundle
//installer when it uncompresses a split archive, then finds it has a real archive
@@ -785,9 +785,9 @@ bool InstallationManager::install(const QString &fileName,
bool archiveOpen = m_ArchiveHandler->open(fileName,
new MethodCallback<InstallationManager, void, QString *>(this, &InstallationManager::queryPassword));
if (!archiveOpen) {
- qDebug("integrated archiver can't open %s: %s (%d)",
- qUtf8Printable(fileName),
- qUtf8Printable(getErrorString(m_ArchiveHandler->getLastError())),
+ log::debug("integrated archiver can't open {}: {} ({})",
+ fileName,
+ getErrorString(m_ArchiveHandler->getLastError()),
m_ArchiveHandler->getLastError());
}
ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
@@ -856,8 +856,7 @@ bool InstallationManager::install(const QString &fileName,
}
}
} catch (const IncompatibilityException &e) {
- qCritical("plugin \"%s\" incompatible: %s",
- qUtf8Printable(installer->name()), e.what());
+ log::error("plugin \"{}\" incompatible: {}", installer->name(), e.what());
}
// act upon the installation result. at this point the files have already been
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index ddc2d067..fdc30e22 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "selectiondialog.h"
#include <utility.h>
+#include <log.h>
#include <appconfig.h>
#include <QCoreApplication>
#include <QDir>
@@ -29,13 +30,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QMessageBox>
#include <cstdint>
+using namespace MOBase;
static const char COMPANY_NAME[] = "Tannin";
static const char APPLICATION_NAME[] = "Mod Organizer";
static const char INSTANCE_KEY[] = "CurrentInstance";
-
InstanceManager::InstanceManager()
: m_AppSettings(COMPANY_NAME, APPLICATION_NAME)
{
@@ -86,10 +87,13 @@ bool InstanceManager::deleteLocalInstance(const QString &instanceId) const
if (!MOBase::shellDelete(QStringList(instancePath),true))
{
- qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(instancePath), ::GetLastError());
+ log::warn(
+ "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete",
+ instancePath, ::GetLastError());
+
if (!MOBase::removeDir(instancePath))
{
- qWarning("regular delete failed too");
+ log::warn("regular delete failed too");
result = false;
}
}
@@ -153,7 +157,7 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons
dialogText = dialog.textValue();
instanceId = sanitizeInstanceName(dialogText);
if (instanceId != dialogText) {
- if (QMessageBox::question( nullptr,
+ if (QMessageBox::question( nullptr,
QObject::tr("Invalid instance name"),
QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
@@ -220,7 +224,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint);
if (selection.exec() == QDialog::Rejected) {
- qDebug("rejected");
+ log::debug("rejected");
throw MOBase::MyException(QObject::tr("Canceled"));
}
@@ -323,7 +327,7 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const
// Don't end in spaces and periods
new_name = new_name.remove(QRegExp("\\.*$"));
new_name = new_name.remove(QRegExp(" *$"));
-
+
// Recurse until stuff stops changing
if (new_name != name) {
return sanitizeInstanceName(new_name);
diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp
index 8f0529ce..4d6cebd4 100644
--- a/src/loadmechanism.cpp
+++ b/src/loadmechanism.cpp
@@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <iplugingame.h>
#include <scriptextender.h>
#include <appconfig.h>
+#include <log.h>
#include <QFile>
#include <QFileInfo>
#include <QDir>
@@ -141,7 +142,7 @@ void LoadMechanism::deactivateScriptExtender()
{
vfsDLLName = ToQString(AppConfig::vfs64DLLName());
}
- qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1());
+ log::debug("USVFS DLL Name: {}", vfsDLLName);
if (vfsDLLName != "") {
if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) {
// remove dll from SE plugins directory
@@ -215,8 +216,8 @@ void LoadMechanism::activateScriptExtender()
QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL));
QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL);
- qDebug("DLL USVFS Target Path: " + targetPath.toLatin1());
- qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1());
+ log::debug("DLL USVFS Target Path: {}", targetPath);
+ log::debug("DLL USVFS VFS DLL Path: {}", vfsDLLPath);
QFile dllFile(targetPath);
@@ -297,17 +298,17 @@ void LoadMechanism::activate(EMechanism mechanism)
{
switch (mechanism) {
case LOAD_MODORGANIZER: {
- qDebug("Load Mechanism: Mod Organizer");
+ log::debug("Load Mechanism: Mod Organizer");
deactivateProxyDLL();
deactivateScriptExtender();
} break;
case LOAD_SCRIPTEXTENDER: {
- qDebug("Load Mechanism: ScriptExtender");
+ log::debug("Load Mechanism: ScriptExtender");
deactivateProxyDLL();
activateScriptExtender();
} break;
case LOAD_PROXYDLL: {
- qDebug("Load Mechanism: Proxy DLL");
+ log::debug("Load Mechanism: Proxy DLL");
deactivateScriptExtender();
activateProxyDLL();
} break;
diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp
deleted file mode 100644
index dfe8f943..00000000
--- a/src/logbuffer.cpp
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "logbuffer.h"
-#include <scopeguard.h>
-#include <report.h>
-#include <QMutexLocker>
-#include <QFile>
-#include <QIcon>
-#include <QDateTime>
-#include <Windows.h>
-
-using MOBase::reportError;
-
-QScopedPointer<LogBuffer> LogBuffer::s_Instance;
-QMutex LogBuffer::s_Mutex;
-
-LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType,
- const QString &outputFileName)
- : QAbstractItemModel(nullptr)
- , m_OutFileName(outputFileName)
- , m_ShutDown(false)
- , m_MinMsgType(minMsgType)
- , m_NumMessages(0)
-{
- m_Messages.resize(messageCount);
-}
-
-LogBuffer::~LogBuffer()
-{
- qInstallMessageHandler(0);
- write();
-}
-
-void LogBuffer::logMessage(QtMsgType type, const QString &message)
-{
- if (type >= m_MinMsgType) {
- QStringList messagelist = message.split("\n");
- for (auto split_message : messagelist) {
- Message msg = {type, QTime::currentTime(), split_message};
- if (m_NumMessages < m_Messages.size()) {
- beginInsertRows(QModelIndex(), static_cast<int>(m_NumMessages),
- static_cast<int>(m_NumMessages) + 1);
- }
- m_Messages.at(m_NumMessages % m_Messages.size()) = msg;
- if (m_NumMessages < m_Messages.size()) {
- endInsertRows();
- } else {
- emit dataChanged(createIndex(0, 0),
- createIndex(static_cast<int>(m_Messages.size()), 0));
- }
- ++m_NumMessages;
- if (type >= QtCriticalMsg) {
- write();
- }
- }
- }
-}
-
-void LogBuffer::write() const
-{
- if (m_NumMessages == 0) {
- return;
- }
-
- DWORD lastError = ::GetLastError();
-
- QFile file(m_OutFileName);
- if (!file.open(QIODevice::WriteOnly)) {
- reportError(tr("failed to write log to %1: %2")
- .arg(m_OutFileName)
- .arg(file.errorString()));
- return;
- }
-
- unsigned int i
- = (m_NumMessages > m_Messages.size())
- ? static_cast<unsigned int>(m_NumMessages - m_Messages.size())
- : 0U;
- for (; i < m_NumMessages; ++i) {
- file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8());
- file.write("\r\n");
- }
- ::SetLastError(lastError);
-}
-
-void LogBuffer::init(int messageCount, QtMsgType minMsgType,
- const QString &outputFileName)
-{
- QMutexLocker guard(&s_Mutex);
-
- s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName));
- qInstallMessageHandler(LogBuffer::log);
-}
-
-char LogBuffer::msgTypeID(QtMsgType type)
-{
- switch (type) {
- case QtDebugMsg:
- return 'D';
- case QtInfoMsg:
- return 'I';
- case QtWarningMsg:
- return 'W';
- case QtCriticalMsg:
- return 'C';
- case QtFatalMsg:
- return 'F';
- default:
- return '?';
- }
-}
-
-void LogBuffer::log(QtMsgType type, const QMessageLogContext &context,
- const QString &message)
-{
- // QMutexLocker doesn't support timeout...
- if (!s_Mutex.tryLock(100)) {
- fprintf(stderr, "failed to log: %s", qUtf8Printable(message));
- return;
- }
- ON_BLOCK_EXIT([]() { s_Mutex.unlock(); });
-
- if (!s_Instance.isNull()) {
- s_Instance->logMessage(type, message);
- }
-
- if (type == QtDebugMsg) {
- fprintf(stdout, "%s [%c] %s\n", qUtf8Printable(QTime::currentTime().toString()),
- msgTypeID(type), qUtf8Printable(message));
- } else {
- if (context.line != 0) {
- fprintf(stdout, "%s [%c] (%s:%u) %s\n",
- qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type),
- context.file, context.line, qUtf8Printable(message));
- } else {
- fprintf(stdout, "%s [%c] %s\n",
- qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type),
- qUtf8Printable(message));
- }
- }
- fflush(stdout);
-}
-
-QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const
-{
- return createIndex(row, column, row);
-}
-
-QModelIndex LogBuffer::parent(const QModelIndex &) const
-{
- return QModelIndex();
-}
-
-int LogBuffer::rowCount(const QModelIndex &parent) const
-{
- if (parent.isValid())
- return 0;
- else
- return static_cast<int>(std::min(m_NumMessages, m_Messages.size()));
-}
-
-int LogBuffer::columnCount(const QModelIndex &) const
-{
- return 2;
-}
-
-QVariant LogBuffer::data(const QModelIndex &index, int role) const
-{
- unsigned int offset
- = m_NumMessages < m_Messages.size()
- ? 0
- : static_cast<unsigned int>(m_NumMessages - m_Messages.size());
- unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size();
- switch (role) {
- case Qt::DisplayRole: {
- if (index.column() == 0) {
- return m_Messages[msgIndex].time.toString("H: mm: ss");
- } else if (index.column() == 1) {
- const QString &msg = m_Messages[msgIndex].message;
- if (msg.length() < 200) {
- return msg;
- } else {
- return msg.mid(0, 200) + "...";
- }
- }
- } break;
- case Qt::DecorationRole: {
- if (index.column() == 1) {
- switch (m_Messages[msgIndex].type) {
- case QtDebugMsg:
- case QtInfoMsg:
- return QIcon(":/MO/gui/information");
- case QtWarningMsg:
- return QIcon(":/MO/gui/warning");
- case QtCriticalMsg:
- return QIcon(":/MO/gui/important");
- case QtFatalMsg:
- return QIcon(":/MO/gui/problem");
- }
- }
- } break;
- case Qt::UserRole: {
- if (index.column() == 1) {
- switch (m_Messages[msgIndex].type) {
- case QtDebugMsg:
- return "D";
- case QtInfoMsg:
- return "I";
- case QtWarningMsg:
- return "W";
- case QtCriticalMsg:
- return "C";
- case QtFatalMsg:
- return "F";
- }
- }
- } break;
- }
- return QVariant();
-}
-
-void LogBuffer::writeNow()
-{
- QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance->write();
- }
-}
-
-void LogBuffer::cleanQuit()
-{
- QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance->m_ShutDown = true;
- }
-}
-
-void log(const char *format, ...)
-{
- va_list argList;
- va_start(argList, format);
-
- static const int BUFFERSIZE = 1000;
-
- char buffer[BUFFERSIZE + 1];
- buffer[BUFFERSIZE] = '\0';
-
- vsnprintf(buffer, BUFFERSIZE, format, argList);
-
- qCritical("%s", buffer);
-
- va_end(argList);
-}
-
-QString LogBuffer::Message::toString() const
-{
- return QString("%1 [%2] %3")
- .arg(time.toString())
- .arg(msgTypeID(type))
- .arg(message);
-}
diff --git a/src/logbuffer.h b/src/logbuffer.h
deleted file mode 100644
index 0cfecfa2..00000000
--- a/src/logbuffer.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef LOGBUFFER_H
-#define LOGBUFFER_H
-
-#include <QObject>
-#include <QMutex>
-#include <QScopedPointer>
-#include <QStringListModel>
-#include <QTime>
-#include <vector>
-
-
-class LogBuffer : public QAbstractItemModel
-{
- Q_OBJECT
-
-public:
-
- static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
- static void log(QtMsgType type, const QMessageLogContext &context, const QString &message);
-
- static void writeNow();
- static void cleanQuit();
-
- static LogBuffer *instance() { return s_Instance.data(); }
-
-public:
-
- virtual ~LogBuffer();
-
- void logMessage(QtMsgType type, const QString &message);
-
- // QAbstractItemModel interface
-public:
- QModelIndex index(int row, int column, const QModelIndex &parent) const;
- QModelIndex parent(const QModelIndex &child) const;
- int rowCount(const QModelIndex &parent) const;
- int columnCount(const QModelIndex &parent) const;
- QVariant data(const QModelIndex &index, int role) const;
-
-signals:
-
-public slots:
-
-private:
-
- explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
- LogBuffer(const LogBuffer &reference); // not implemented
- LogBuffer &operator=(const LogBuffer &reference); // not implemented
-
- void write() const;
-
- static char msgTypeID(QtMsgType type);
-
-private:
-
- struct Message {
- QtMsgType type;
- QTime time;
- QString message;
- QString toString() const;
- };
-
-private:
-
- static QScopedPointer<LogBuffer> s_Instance;
- static QMutex s_Mutex;
-
- QString m_OutFileName;
- bool m_ShutDown;
- QtMsgType m_MinMsgType;
- size_t m_NumMessages;
- std::vector<Message> m_Messages;
-
-};
-
-#endif // LOGBUFFER_H
diff --git a/src/loglist.cpp b/src/loglist.cpp
new file mode 100644
index 00000000..192913b6
--- /dev/null
+++ b/src/loglist.cpp
@@ -0,0 +1,254 @@
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "loglist.h"
+#include "organizercore.h"
+
+using namespace MOBase;
+
+static LogModel* g_instance = nullptr;
+const std::size_t MaxLines = 1000;
+
+LogModel::LogModel()
+{
+ connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); });
+}
+
+void LogModel::create()
+{
+ g_instance = new LogModel;
+}
+
+LogModel& LogModel::instance()
+{
+ return *g_instance;
+}
+
+void LogModel::add(MOBase::log::Entry e)
+{
+ emit entryAdded(std::move(e));
+}
+
+void LogModel::clear()
+{
+ beginResetModel();
+ m_entries.clear();
+ endResetModel();
+}
+
+const std::deque<MOBase::log::Entry>& LogModel::entries() const
+{
+ return m_entries;
+}
+
+void LogModel::onEntryAdded(MOBase::log::Entry e)
+{
+ bool full = false;
+ if (m_entries.size() > MaxLines) {
+ m_entries.pop_front();
+ full = true;
+ }
+
+ const int row = static_cast<int>(m_entries.size());
+
+ if (!full) {
+ beginInsertRows(QModelIndex(), row, row + 1);
+ }
+
+ m_entries.emplace_back(std::move(e));
+
+ if (!full) {
+ endInsertRows();
+ } else {
+ emit dataChanged(
+ createIndex(row, 0),
+ createIndex(row + 1, columnCount({})));
+ }
+}
+
+QModelIndex LogModel::index(int row, int column, const QModelIndex&) const
+{
+ return createIndex(row, column, row);
+}
+
+QModelIndex LogModel::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+int LogModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ else
+ return static_cast<int>(m_entries.size());
+}
+
+int LogModel::columnCount(const QModelIndex&) const
+{
+ return 3;
+}
+
+QVariant LogModel::data(const QModelIndex& index, int role) const
+{
+ using namespace std::chrono;
+
+ const auto row = static_cast<std::size_t>(index.row());
+ if (row >= m_entries.size()) {
+ return {};
+ }
+
+ const auto& e = m_entries[row];
+
+ if (role == Qt::DisplayRole) {
+ if (index.column() == 1) {
+ const auto ms = duration_cast<milliseconds>(e.time.time_since_epoch());
+ const auto s = duration_cast<seconds>(ms);
+
+ const std::time_t tt = s.count();
+ const int frac = static_cast<int>(ms.count() % 1000);
+
+ const auto time = QDateTime::fromTime_t(tt).time().addMSecs(frac);
+ return time.toString("hh:mm:ss.zzz");
+ } else if (index.column() == 2) {
+ return QString::fromStdString(e.message);
+ }
+ }
+
+ if (role == Qt::DecorationRole) {
+ if (index.column() == 0) {
+ switch (e.level) {
+ case log::Warning:
+ return QIcon(":/MO/gui/warning");
+
+ case log::Error:
+ return QIcon(":/MO/gui/problem");
+
+ case log::Debug: // fall-through
+ case log::Info:
+ default:
+ return {};
+ }
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant LogModel::headerData(int, Qt::Orientation, int) const
+{
+ return {};
+}
+
+
+LogList::LogList(QWidget* parent)
+ : QTreeView(parent), m_core(nullptr)
+{
+ setModel(&LogModel::instance());
+
+ const int timestampWidth = QFontMetrics(font()).width("00:00:00.000");
+
+ header()->setMinimumSectionSize(0);
+ header()->resizeSection(0, 20);
+ header()->resizeSection(1, timestampWidth + 8);
+
+ setAutoScroll(true);
+ scrollToBottom();
+
+ connect(
+ this, &QWidget::customContextMenuRequested,
+ [&](auto&& pos){ onContextMenu(pos); });
+
+ connect(
+ model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(scrollToBottom()));
+
+ connect(
+ model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(scrollToBottom()));
+}
+
+void LogList::setCore(OrganizerCore& core)
+{
+ m_core = &core;
+}
+
+void LogList::copyToClipboard()
+{
+ std::string s;
+
+ auto* m = static_cast<LogModel*>(model());
+ for (const auto& e : m->entries()) {
+ s += e.formattedMessage + "\n";
+ }
+
+ if (!s.empty()) {
+ // last newline
+ s.pop_back();
+ }
+
+ QApplication::clipboard()->setText(QString::fromStdString(s));
+}
+
+void LogList::clear()
+{
+ static_cast<LogModel*>(model())->clear();
+}
+
+QMenu* LogList::createMenu(QWidget* parent)
+{
+ auto* menu = new QMenu(parent);
+
+ menu->addAction(tr("&Copy all"), [&]{ copyToClipboard(); });
+ menu->addSeparator();
+ menu->addAction(tr("C&lear all"), [&]{ clear(); });
+
+ auto* levels = new QMenu(tr("&Level"));
+ menu->addMenu(levels);
+
+ auto* ag = new QActionGroup(menu);
+
+ auto addAction = [&](auto&& text, auto&& level) {
+ auto* a = new QAction(text, ag);
+
+ a->setCheckable(true);
+ a->setChecked(log::getDefault().level() == level);
+
+ connect(a, &QAction::triggered, [this, level]{
+ if (m_core) {
+ m_core->setLogLevel(level);
+ }
+ });
+
+ levels->addAction(a);
+ };
+
+ addAction(tr("&Debug"), log::Debug);
+ addAction(tr("&Info"), log::Info);
+ addAction(tr("&Warnings"), log::Warning);
+ addAction(tr("&Errors"), log::Error);
+
+ return menu;
+}
+
+void LogList::onContextMenu(const QPoint& pos)
+{
+ auto* menu = createMenu(this);
+ menu->popup(viewport()->mapToGlobal(pos));
+}
diff --git a/src/loglist.h b/src/loglist.h
new file mode 100644
index 00000000..0b25dfd1
--- /dev/null
+++ b/src/loglist.h
@@ -0,0 +1,79 @@
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef LOGBUFFER_H
+#define LOGBUFFER_H
+
+#include <QTreeView>
+#include <log.h>
+
+class OrganizerCore;
+
+class LogModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ static void create();
+ static LogModel& instance();
+
+ void add(MOBase::log::Entry e);
+ void clear();
+
+ const std::deque<MOBase::log::Entry>& entries() const;
+
+protected:
+ QModelIndex index(int row, int column, const QModelIndex& parent) const override;
+ QModelIndex parent(const QModelIndex &child) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QVariant headerData(
+ int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override;
+
+signals:
+ void entryAdded(MOBase::log::Entry e);
+
+private:
+ std::deque<MOBase::log::Entry> m_entries;
+
+ LogModel();
+ void onEntryAdded(MOBase::log::Entry e);
+};
+
+
+class LogList : public QTreeView
+{
+public:
+ LogList(QWidget* parent=nullptr);
+
+ void setCore(OrganizerCore& core);
+
+ void copyToClipboard();
+ void clear();
+
+ QMenu* createMenu(QWidget* parent=nullptr);
+
+private:
+ OrganizerCore* m_core;
+ void onContextMenu(const QPoint& pos);
+};
+
+#endif // LOGBUFFER_H
diff --git a/src/main.cpp b/src/main.cpp
index bf4e5b97..0e61a781 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -39,7 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "singleinstance.h"
#include "utility.h"
#include "helper.h"
-#include "logbuffer.h"
+#include "loglist.h"
#include "selectiondialog.h"
#include "moapplication.h"
#include "tutorialmanager.h"
@@ -47,10 +47,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "moshortcut.h"
#include "organizercore.h"
+#include "env.h"
+#include "envmodule.h"
#include <eh.h>
#include <windows_error.h>
#include <usvfs.h>
+#include <log.h>
#include <QApplication>
#include <QPushButton>
@@ -132,9 +135,9 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
int dumpRes =
CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
if (!dumpRes)
- qCritical("ModOrganizer has crashed, crash dump created.");
+ log::error("ModOrganizer has crashed, crash dump created.");
else
- qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError());
+ log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError());
if (prevUnhandledExceptionFilter)
return prevUnhandledExceptionFilter(exceptionPtrs);
@@ -252,17 +255,17 @@ QString determineProfile(QStringList &arguments, const QSettings &settings)
{ // see if there is a profile on the command line
int profileIndex = arguments.indexOf("-p", 1);
if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) {
- qDebug("profile overwritten on command line");
+ log::debug("profile overwritten on command line");
selectedProfileName = arguments.at(profileIndex + 1);
}
arguments.removeAt(profileIndex);
arguments.removeAt(profileIndex);
}
if (selectedProfileName.isEmpty()) {
- qDebug("no configured profile");
+ log::debug("no configured profile");
selectedProfileName = "Default";
} else {
- qDebug("configured profile: %s", qUtf8Printable(selectedProfileName));
+ log::debug("configured profile: {}", selectedProfileName);
}
return selectedProfileName;
@@ -424,9 +427,6 @@ void setupPath()
{
static const int BUFSIZE = 4096;
- qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators(
- QCoreApplication::applicationDirPath())));
-
QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths());
boost::scoped_array<TCHAR> oldPath(new TCHAR[BUFSIZE]);
@@ -446,8 +446,6 @@ void setupPath()
void preloadDll(const QString& filename)
{
- qDebug().nospace() << "preloading " << filename;
-
if (GetModuleHandleW(filename.toStdWString().c_str())) {
// already loaded, this can happen when "restarting" MO by switching
// instances, for example
@@ -460,16 +458,13 @@ void preloadDll(const QString& filename)
const auto dllPath = appPath + "\\" + filename;
if (!QFile::exists(dllPath)) {
- qWarning().nospace() << dllPath << "not found";
+ log::warn("{} not found", dllPath);
return;
}
if (!LoadLibraryW(dllPath.toStdWString().c_str())) {
const auto e = GetLastError();
-
- qWarning().nospace()
- << "failed to load " << dllPath << ": "
- << formatSystemMessage(e);
+ log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e));
}
}
@@ -489,84 +484,129 @@ static QString getVersionDisplayString()
return createVersionInfo().displayString(3);
}
-int runApplication(MOApplication &application, SingleInstance &instance,
- const QString &splashPath)
+void dumpSettings(QSettings& settings)
{
+ static const QStringList ignore({
+ "username", "password", "nexus_api_key"
+ });
- qDebug().nospace()
- << "Starting Mod Organizer version "
- << getVersionDisplayString() << " revision " << GITID;
+ log::debug("settings:");
-#if !defined(QT_NO_SSL)
- preloadSsl();
- qDebug("ssl support: %d", QSslSocket::supportsSsl());
-#else
- qDebug("non-ssl build");
-#endif
+ settings.beginGroup("Settings");
- {
- env::Environment env;
+ for (auto k : settings.allKeys()) {
+ if (ignore.contains(k, Qt::CaseInsensitive)) {
+ continue;
+ }
- qDebug().nospace().noquote()
- << "windows: " << env.windowsInfo().toString();
+ log::debug(" . {}={}", k, settings.value(k).toString());
+ }
- if (env.windowsInfo().compatibilityMode()) {
- qWarning() << "MO seems to be running in compatibility mode";
- }
+ settings.endGroup();
+}
- qDebug().nospace().noquote() << "security products:";
- for (const auto& sp : env.securityProducts()) {
- qDebug().nospace().noquote() << " . " << sp.toString();
+void checkMissingFiles()
+{
+ // files that are likely to be eaten
+ static const QStringList files({
+ "helper.exe", "nxmhandler.exe",
+ "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe",
+ "usvfs_x64.dll", "usvfs_x86.dll"
+ });
+
+ const auto dir = QCoreApplication::applicationDirPath();
+
+ for (const auto& name : files) {
+ const QFileInfo file(dir + QDir::separator() + name);
+ if (!file.exists()) {
+ log::warn(
+ "'{}' seems to be missing, an antivirus may have deleted it",
+ file.absoluteFilePath());
}
+ }
+}
+
+void checkNahimic(const env::Environment& e)
+{
+ for (auto&& m : e.loadedModules()) {
+ const QFileInfo file(m.path());
- qDebug() << "modules loaded in process:";
- for (const auto& m : env.loadedModules()) {
- qDebug().nospace().noquote() << " . " << m.toString();
+ if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) {
+ log::warn(
+ "NahimicOSD.dll is loaded. Nahimic is known to cause issues with "
+ "Mod Organizer, such as freezing or blank windows. Consider "
+ "uninstalling it.");
+
+ break;
}
}
+}
+
+void sanityChecks(const env::Environment& e)
+{
+ checkMissingFiles();
+ checkNahimic(e);
+}
+
+
+int runApplication(MOApplication &application, SingleInstance &instance,
+ const QString &splashPath)
+{
+ log::info(
+ "starting Mod Organizer version {} revision {} in {}",
+ getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath());
+
+ preloadSsl();
+ if (!QSslSocket::supportsSsl()) {
+ log::warn("no ssl support");
+ }
QString dataPath = application.property("dataPath").toString();
- qDebug("data path: %s", qUtf8Printable(dataPath));
+ log::info("data path: {}", dataPath);
if (!bootstrap()) {
reportError("failed to set up data paths");
return 1;
}
- QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow);
+ QWindowsWindowFunctions::setWindowActivationBehavior(
+ QWindowsWindowFunctions::AlwaysActivateWindow);
QStringList arguments = application.arguments();
try {
- qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath())));
+ log::info("working directory: {}", QDir::currentPath());
+
+ QSettings initSettings(
+ dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()),
+ QSettings::IniFormat);
- QSettings settings(dataPath + "/"
- + QString::fromStdWString(AppConfig::iniFileName()),
- QSettings::IniFormat);
+ Settings settings(initSettings);
+ log::getDefault().setLevel(settings.logLevel());
- // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime
- OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt());
+ // global crashDumpType sits in OrganizerCore to make a bit less ugly to
+ // update it when the settings are changed during runtime
+ OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType());
- qDebug("Loaded settings:");
- settings.beginGroup("Settings");
- for (auto k : settings.allKeys())
- if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key"))
- qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data());
- settings.endGroup();
+ env::Environment env;
+ env.dump();
+ dumpSettings(initSettings);
+ sanityChecks(env);
- qDebug("initializing core");
+ log::debug("initializing core");
OrganizerCore organizer(settings);
if (!organizer.bootstrap()) {
reportError("failed to set up data paths");
return 1;
}
- qDebug("initialize plugins");
+
+ log::debug("initializing plugins");
PluginContainer pluginContainer(&organizer);
pluginContainer.loadPlugins();
MOBase::IPluginGame *game = determineCurrentGame(
- application.applicationDirPath(), settings, pluginContainer);
+ application.applicationDirPath(), initSettings, pluginContainer);
if (game == nullptr) {
InstanceManager &instance = InstanceManager::instance();
QString instanceName = instance.currentInstance();
@@ -583,15 +623,13 @@ int runApplication(MOApplication &application, SingleInstance &instance,
QImage image(pluginSplash);
if (!image.isNull()) {
image.save(dataPath + "/splash.png");
- } else {
- qDebug("no plugin splash");
}
}
organizer.setManagedGame(game);
organizer.createDefaultProfile();
- if (!settings.contains("game_edition")) {
+ if (!initSettings.contains("game_edition")) {
QStringList editions = game->gameVariants();
if (editions.size() > 1) {
SelectionDialog selection(
@@ -607,18 +645,17 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (selection.exec() == QDialog::Rejected) {
return 1;
} else {
- settings.setValue("game_edition", selection.getChoiceString());
+ initSettings.setValue("game_edition", selection.getChoiceString());
}
}
}
- game->setGameVariant(settings.value("game_edition").toString());
+ game->setGameVariant(initSettings.value("game_edition").toString());
- qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators(
- game->gameDirectory().absolutePath())));
+ log::info("managing game at {}", game->gameDirectory().absolutePath());
- organizer.updateExecutablesList(settings);
+ organizer.updateExecutablesList(initSettings);
- QString selectedProfileName = determineProfile(arguments, settings);
+ QString selectedProfileName = determineProfile(arguments, initSettings);
organizer.setCurrentProfile(selectedProfileName);
// if we have a command line parameter, it is either a nxm link or
@@ -638,13 +675,12 @@ int runApplication(MOApplication &application, SingleInstance &instance,
}
}
else if (OrganizerCore::isNxmLink(arguments.at(1))) {
- qDebug("starting download from command line: %s",
- qUtf8Printable(arguments.at(1)));
+ log::debug("starting download from command line: {}", arguments.at(1));
organizer.externalMessage(arguments.at(1));
}
else {
QString exeName = arguments.at(1);
- qDebug("starting %s from command line", qUtf8Printable(exeName));
+ log::debug("starting {} from command line", exeName);
arguments.removeFirst(); // remove application name (ModOrganizer.exe)
arguments.removeFirst(); // remove binary name
// pass the remaining parameters to the binary
@@ -663,8 +699,8 @@ int runApplication(MOApplication &application, SingleInstance &instance,
QPixmap pixmap(splashPath);
QSplashScreen splash(pixmap);
- if (settings.contains("window_monitor")) {
- const int monitor = settings.value("window_monitor").toInt();
+ if (initSettings.contains("window_monitor")) {
+ const int monitor = initSettings.value("window_monitor").toInt();
if (monitor != -1 && QGuiApplication::screens().size() > monitor) {
QGuiApplication::screens().at(monitor)->geometry().center();
@@ -684,21 +720,21 @@ int runApplication(MOApplication &application, SingleInstance &instance,
NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey);
}
- qDebug("initializing tutorials");
+ log::debug("initializing tutorials");
TutorialManager::init(
qApp->applicationDirPath() + "/"
+ QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
&organizer);
- if (!application.setStyleFile(settings.value("Settings/style", "").toString())) {
+ if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) {
// disable invalid stylesheet
- settings.setValue("Settings/style", "");
+ initSettings.setValue("Settings/style", "");
}
int res = 1;
{ // scope to control lifetime of mainwindow
// set up main window and its data structures
- MainWindow mainWindow(settings, organizer, pluginContainer);
+ MainWindow mainWindow(initSettings, organizer, pluginContainer);
NexusInterface::instance(&pluginContainer)
->getAccessManager()->setTopLevelWidget(&mainWindow);
@@ -709,9 +745,13 @@ int runApplication(MOApplication &application, SingleInstance &instance,
SLOT(externalMessage(QString)));
mainWindow.processUpdates();
+
+ // this must be before readSettings(), see DockFixer in mainwindow.cpp
+ splash.finish(&mainWindow);
+
mainWindow.readSettings();
- qDebug("displaying main window");
+ log::debug("displaying main window");
mainWindow.show();
mainWindow.activateWindow();
@@ -732,16 +772,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
int doCoreDump(env::CoreDumpTypes type)
{
- // open a console
- AllocConsole();
-
- // redirect stdin, stdout and stderr to it
- FILE* in=nullptr;
- FILE* out=nullptr;
- FILE* err=nullptr;
- freopen_s(&in, "CONIN$", "r", stdin);
- freopen_s(&out, "CONOUT$", "w", stdout);
- freopen_s(&err, "CONOUT$", "w", stderr);
+ env::Console c;
// dump
const auto b = env::coredumpOther(type);
@@ -752,17 +783,69 @@ int doCoreDump(env::CoreDumpTypes type)
std::wcerr << L"Press enter to continue...";
std::wcin.get();
- // close redirected handles
- std::fclose(err);
- std::fclose(out);
- std::fclose(in);
+ return (b ? 0 : 1);
+}
+
+log::Levels convertQtLevel(QtMsgType t)
+{
+ switch (t)
+ {
+ case QtDebugMsg:
+ return log::Debug;
- // close console
- FreeConsole();
+ case QtWarningMsg:
+ return log::Warning;
- return (b ? 0 : 1);
+ case QtCriticalMsg: // fall-through
+ case QtFatalMsg:
+ return log::Error;
+
+ case QtInfoMsg: // fall-through
+ default:
+ return log::Info;
+ }
}
+void qtLogCallback(
+ QtMsgType type, const QMessageLogContext& context, const QString& message)
+{
+ std::string_view file = "";
+
+ if (type != QtDebugMsg) {
+ if (context.file) {
+ file = context.file;
+
+ const auto lastSep = file.find_last_of("/\\");
+ if (lastSep != std::string_view::npos) {
+ file = {context.file + lastSep + 1};
+ }
+ }
+ }
+
+ if (file.empty()) {
+ log::log(
+ convertQtLevel(type), "{}",
+ message.toStdString());
+ } else {
+ log::log(
+ convertQtLevel(type), "[{}:{}] {}",
+ file, context.line, message.toStdString());
+ }
+}
+
+void initLogging()
+{
+ LogModel::create();
+
+ log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$");
+
+ log::getDefault().setCallback(
+ [](log::Entry e){ LogModel::instance().add(e); });
+
+ qInstallMessageHandler(qtLogCallback);
+}
+
+
int main(int argc, char *argv[])
{
// handle --crashdump first
@@ -776,6 +859,8 @@ int main(int argc, char *argv[])
}
}
+ initLogging();
+
//Make sure the configured temp folder exists
QDir tempDir = QDir::temp();
if (!tempDir.exists())
@@ -809,7 +894,7 @@ int main(int argc, char *argv[])
if (moshortcut ||
arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1)))
{
- qDebug("not primary instance, sending shortcut/download message");
+ log::debug("not primary instance, sending shortcut/download message");
instance.sendMessage(arguments.at(1));
return 0;
} else if (arguments.size() == 1) {
@@ -838,7 +923,11 @@ int main(int argc, char *argv[])
// initialize dump collection only after "dataPath" since the crashes are stored under it
prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
- LogBuffer::init(1000000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
+ const auto logFile =
+ qApp->property("dataPath").toString() + "/logs/mo_interface.log";
+
+ log::getDefault().setFile(MOBase::log::File::rotating(
+ logFile.toStdWString(), 5*1024*1024, 5));
QString splash = dataPath + "/splash.png";
if (!QFile::exists(dataPath + "/splash.png")) {
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index d06f79f2..7c73bc8a 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -59,7 +59,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "installationmanager.h"
#include "lockeddialog.h"
#include "waitingonclosedialog.h"
-#include "logbuffer.h"
#include "downloadlistsortproxy.h"
#include "motddialog.h"
#include "filedialogmemory.h"
@@ -86,6 +85,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <usvfs.h>
#include "localsavegames.h"
#include "listdialog.h"
+#include "envshortcut.h"
#include <QAbstractItemDelegate>
#include <QAbstractProxyModel>
@@ -194,6 +194,102 @@ const QSize MediumToolbarSize(32, 32);
const QSize LargeToolbarSize(42, 36);
+// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock
+// sizes are not restored when the main window is maximized; it is used in
+// MainWindow::readSettings() and MainWindow::storeSettings()
+//
+// there's also https://stackoverflow.com/questions/44005852, which has what
+// seems to be a popular fix, but it breaks the restored size of the window
+// by setting it to the desktop's resolution, so that doesn't work
+//
+// the only fix I could find is to remember the sizes of the docks and manually
+// setting them back; saving is straightforward, but restoring is messy
+//
+// this also depends on the window being visible before the timer in restore()
+// is fired and the timer must be processed by application.exec(); therefore,
+// the splash screen _must_ be closed before readSettings() is called, because
+// it has its own event loop, which seems to interfere with this
+//
+// all of this should become unnecessary when QTBUG-46620 is fixed
+//
+class DockFixer
+{
+public:
+ static void save(MainWindow* mw, QSettings& settings)
+ {
+ const auto docks = mw->findChildren<QDockWidget*>();
+
+ // saves the size of each dock
+ for (int i=0; i<docks.size(); ++i) {
+ int size = 0;
+
+ // save the width for horizontal docks, or the height for vertical
+ if (orientation(mw, docks[i]) == Qt::Horizontal) {
+ size = docks[i]->size().width();
+ } else {
+ size = docks[i]->size().height();
+ }
+
+ settings.setValue(settingName(docks[i]), size);
+ }
+ }
+
+ static void restore(MainWindow* mw, const QSettings& settings)
+ {
+ struct DockInfo
+ {
+ QDockWidget* d;
+ int size = 0;
+ Qt::Orientation ori;
+ };
+
+ std::vector<DockInfo> dockInfos;
+
+ const auto docks = mw->findChildren<QDockWidget*>();
+
+ // for each dock
+ for (int i=0; i<docks.size(); ++i) {
+ const QString name = settingName(docks[i]);
+
+ if (settings.contains(name)) {
+ // remember this dock, its size and orientation
+ const auto size = settings.value(name).toInt();
+ dockInfos.push_back({docks[i], size, orientation(mw, docks[i])});
+ }
+ }
+
+ // the main window must have had time to process the settings from
+ // readSettings() or it seems to override whatever is set here
+ //
+ // some people said a single processEvents() call is enough, but it doesn't
+ // look like it
+ QTimer::singleShot(1, [=] {
+ for (const auto& info : dockInfos) {
+ mw->resizeDocks({info.d}, {info.size}, info.ori);
+ }
+ });
+ }
+
+ static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d)
+ {
+ // docks in these areas are horizontal
+ const auto horizontalAreas =
+ Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea;
+
+ if (mw->dockWidgetArea(d) & horizontalAreas) {
+ return Qt::Horizontal;
+ } else {
+ return Qt::Vertical;
+ }
+ }
+
+ static QString settingName(QDockWidget* d)
+ {
+ return "geometry/" + d->objectName() + "_size";
+ }
+};
+
+
MainWindow::MainWindow(QSettings &initSettings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
@@ -260,17 +356,10 @@ MainWindow::MainWindow(QSettings &initSettings
m_CategoryFactory.loadCategories();
- ui->logList->setModel(LogBuffer::instance());
- ui->logList->setColumnWidth(0, 100);
- ui->logList->setAutoScroll(true);
- ui->logList->scrollToBottom();
- ui->logList->addAction(ui->actionCopy_Log_to_Clipboard);
+ ui->logList->setCore(m_OrganizerCore);
+
int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value
ui->topLevelSplitter->setSizes(QList<int>() << splitterSize - 100 << 100);
- connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
- ui->logList, SLOT(scrollToBottom()));
- connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- ui->logList, SLOT(scrollToBottom()));
updateProblemsButton();
@@ -421,7 +510,8 @@ MainWindow::MainWindow(QSettings &initSettings
connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int)));
connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder)));
connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint)));
- connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); });
+ connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); });
+ connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); });
connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled);
connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close);
@@ -708,7 +798,7 @@ void MainWindow::setupToolbar()
ui->toolBar->insertWidget(m_linksSeparator, spacer);
} else {
- qWarning("no separator found on the toolbar, icons won't be right-aligned");
+ log::warn("no separator found on the toolbar, icons won't be right-aligned");
}
}
@@ -745,7 +835,7 @@ void MainWindow::updatePinnedExecutables()
exeAction->setStatusTip(exe.binaryInfo().filePath());
if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) {
- qDebug("failed to connect trigger?");
+ log::debug("failed to connect trigger?");
}
if (m_linksSeparator) {
@@ -763,7 +853,7 @@ void MainWindow::updatePinnedExecutables()
ui->menuRun->menuAction()->setVisible(hasLinks);
}
-void MainWindow::toolbarMenu_aboutToShow()
+void MainWindow::updateToolbarMenu()
{
// well, this is a bit of a hack to allow the same toolbar menu to be shown
// in both the main menu and the context menu
@@ -787,6 +877,11 @@ void MainWindow::toolbarMenu_aboutToShow()
ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon);
}
+void MainWindow::updateViewMenu()
+{
+ ui->actionViewLog->setChecked(ui->logDock->isVisible());
+}
+
QMenu* MainWindow::createPopupMenu()
{
return ui->menuToolbars;
@@ -837,6 +932,11 @@ void MainWindow::on_actionToolBarIconsAndText_triggered()
setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon);
}
+void MainWindow::on_actionViewLog_triggered()
+{
+ ui->logDock->setVisible(!ui->logDock->isVisible());
+}
+
void MainWindow::setToolbarSize(const QSize& s)
{
for (auto* tb : findChildren<QToolBar*>()) {
@@ -1080,14 +1180,14 @@ void MainWindow::createHelpMenu()
QFile file(dirIter.filePath());
if (!file.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open " << fileName;
+ log::error("Failed to open {}", fileName);
continue;
}
QString firstLine = QString::fromUtf8(file.readLine());
if (firstLine.startsWith("//TL")) {
QStringList params = firstLine.mid(4).trimmed().split('#');
if (params.size() != 2) {
- qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters";
+ log::error("invalid header line for tutorial {}, expected 2 parameters", fileName);
continue;
}
QAction *tutAction = new QAction(params.at(0), tutorialMenu);
@@ -1191,7 +1291,7 @@ void MainWindow::hookUpWindowTutorials()
QString fileName = dirIter.fileName();
QFile file(dirIter.filePath());
if (!file.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open " << fileName;
+ log::error("Failed to open {}", fileName);
continue;
}
QString firstLine = QString::fromUtf8(file.readLine());
@@ -1237,7 +1337,7 @@ void MainWindow::showEvent(QShowEvent *event)
TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial);
}
} else {
- qCritical() << firstStepsTutorial << " missing";
+ log::error("{} missing", firstStepsTutorial);
QPoint pos = ui->toolBar->mapToGlobal(QPoint());
pos.rx() += ui->toolBar->width() / 2;
pos.ry() += ui->toolBar->height();
@@ -1256,7 +1356,7 @@ void MainWindow::showEvent(QShowEvent *event)
m_OrganizerCore.settings().registerAsNXMHandler(false);
m_WasVisible = true;
- updateProblemsButton();
+ updateProblemsButton();
}
}
@@ -1300,11 +1400,6 @@ bool MainWindow::confirmExit()
void MainWindow::cleanup()
{
- if (ui->logList->model() != nullptr) {
- disconnect(ui->logList->model(), nullptr, nullptr, nullptr);
- ui->logList->setModel(nullptr);
- }
-
QWebEngineProfile::defaultProfile()->clearAllVisitedLinks();
m_IntegratedBrowser.close();
m_SaveMetaTimer.stop();
@@ -1509,7 +1604,7 @@ void MainWindow::startExeAction()
QAction *action = qobject_cast<QAction*>(sender());
if (action == nullptr) {
- qCritical("not an action?");
+ log::error("not an action?");
return;
}
@@ -1519,9 +1614,7 @@ void MainWindow::startExeAction()
auto itor = list.find(title);
if (itor == list.end()) {
- qWarning().nospace()
- << "startExeAction(): executable '" << title << "' not found";
-
+ log::warn("startExeAction(): executable '{}' not found", title);
return;
}
@@ -1586,7 +1679,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index)
// ensure the new index is valid
if (index < 0 || index >= ui->profileBox->count()) {
- qDebug("invalid profile index, using last profile");
+ log::debug("invalid profile index, using last profile");
ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1);
}
@@ -1747,17 +1840,18 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item)
if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) {
// read the data we need from the sub-item, then dispose of it
QTreeWidgetItem *onDemandDataItem = item->child(0);
- std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString());
+ const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString();
+ std::wstring wspath = path.toStdWString();
bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool();
- std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0));
+ std::wstring virtualPath = (wspath + L"\\").substr(6) + ToWString(item->text(0));
DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath);
if (dir != nullptr) {
QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder);
QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File);
- updateTo(item, path, *dir, conflictsOnly, &fileIcon, &folderIcon);
+ updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon);
} else {
- qWarning("failed to update view of %ls", path.c_str());
+ log::warn("failed to update view of {}", path);
}
m_RemoveWidget.push_back(item);
QTimer::singleShot(5, this, SLOT(delayedRemove()));
@@ -1934,7 +2028,7 @@ void MainWindow::refreshSaveList()
QDir savesDir = currentSavesDir();
savesDir.setNameFilters(filters);
- qDebug("reading save games from %s", qUtf8Printable(savesDir.absolutePath()));
+ log::debug("reading save games from {}", savesDir.absolutePath());
QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time);
for (const QFileInfo &file : files) {
@@ -2135,15 +2229,6 @@ void MainWindow::fixCategories()
void MainWindow::setupNetworkProxy(bool activate)
{
QNetworkProxyFactory::setUseSystemConfiguration(activate);
-/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest);
- query.setProtocolTag("http");
- QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(query);
- if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) {
- qDebug("Using proxy: %s", qUtf8Printable(proxies.at(0).hostName()));
- QNetworkProxy::setApplicationProxy(proxies[0]);
- } else {
- qDebug("Not using proxy");
- }*/
}
@@ -2208,6 +2293,8 @@ void MainWindow::readSettings()
if (settings.value("Settings/use_proxy", false).toBool()) {
activateProxy(true);
}
+
+ DockFixer::restore(this, settings);
}
void MainWindow::processUpdates() {
@@ -2251,10 +2338,17 @@ void MainWindow::processUpdates() {
if (currentVersion > lastVersion) {
//NOP
- } else if (currentVersion < lastVersion)
- qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). "
- "The GUI may not downgrade gracefully, so you may experience oddities. "
- "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString();
+ } else if (currentVersion < lastVersion) {
+ const auto text = tr(
+ "Notice: Your current MO version (%1) is lower than the previously used one (%2). "
+ "The GUI may not downgrade gracefully, so you may experience oddities. "
+ "However, there should be no serious issues.")
+ .arg(currentVersion.toString())
+ .arg(lastVersion.toString());
+
+ log::warn("{}", text);
+ }
+
//save version in all case
settings.setValue("version", currentVersion.toString());
}
@@ -2291,10 +2385,13 @@ void MainWindow::storeSettings(QSettings &settings) {
settings.setValue("log_split", ui->topLevelSplitter->saveState());
settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry());
settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked());
+
for (const std::pair<QString, QHeaderView*> kv : m_PersistedGeometry) {
QString key = QString("geometry/") + kv.first;
settings.setValue(key, kv.second->saveState());
}
+
+ DockFixer::save(this, settings);
}
}
@@ -2320,7 +2417,7 @@ void MainWindow::unlock()
{
//If you come through here with a null lock pointer, it's a bug!
if (m_LockDialog == nullptr) {
- qDebug("Unlocking main window when already unlocked");
+ log::debug("Unlocking main window when already unlocked");
return;
}
--m_LockCount;
@@ -2575,7 +2672,7 @@ void MainWindow::directory_refreshed()
if (ui->tabWidget->currentIndex() == 2) {
refreshDataTreeKeepExpandedNodes();
}
-
+
}
void MainWindow::esplist_changed()
@@ -2794,7 +2891,7 @@ void MainWindow::refreshFilters()
while (currentID != 0) {
categoriesUsed.insert(currentID);
if (!cycleTest.insert(currentID).second) {
- qWarning("cycle in categories: %s", qUtf8Printable(SetJoin(cycleTest, ", ")));
+ log::warn("cycle in categories: {}", SetJoin(cycleTest, ", "));
break;
}
currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID));
@@ -3123,7 +3220,7 @@ void MainWindow::displayModInformation(
ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID)
{
if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) {
- qDebug("A different mod information dialog is open. If this is incorrect, please restart MO");
+ log::debug("A different mod information dialog is open. If this is incorrect, please restart MO");
return;
}
std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
@@ -3279,7 +3376,7 @@ void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tab
{
unsigned int index = ModInfo::getIndex(modName);
if (index == UINT_MAX) {
- qCritical("failed to resolve mod name %s", qUtf8Printable(modName));
+ log::error("failed to resolve mod name {}", modName);
return;
}
@@ -3364,7 +3461,7 @@ void MainWindow::visitOnNexus_clicked()
if (modID > 0) {
linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName));
} else {
- qCritical() << "mod '" << info->name() << "' has no nexus id";
+ log::error("mod '{}' has no nexus id", info->name());
}
}
}
@@ -3878,7 +3975,7 @@ void MainWindow::moveOverwriteContentToExistingMod()
}
if (modAbsolutePath.isNull()) {
- qWarning("Mod %s has not been found, for some reason", qUtf8Printable(result));
+ log::warn("Mod {} has not been found, for some reason", result);
return;
}
@@ -3902,7 +3999,8 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath)
MessageDialog::showMessage(tr("Move successful."), this);
}
else {
- qCritical("Move operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError())));
+ const auto e = GetLastError();
+ log::error("Move operation failed: {}", formatSystemMessage(e));
}
m_OrganizerCore.refreshModList();
@@ -3931,7 +4029,8 @@ void MainWindow::clearOverwrite()
updateProblemsButton();
m_OrganizerCore.refreshModList();
} else {
- qCritical("Delete operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError())));
+ const auto e = GetLastError();
+ log::error("Delete operation failed: {}", formatSystemMessage(e));
}
}
}
@@ -4175,7 +4274,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere
void MainWindow::addRemoveCategories_MenuHandler() {
QMenu *menu = qobject_cast<QMenu*>(sender());
if (menu == nullptr) {
- qCritical("not a menu?");
+ log::error("not a menu?");
return;
}
@@ -4189,7 +4288,7 @@ void MainWindow::addRemoveCategories_MenuHandler() {
int maxRow = -1;
for (const QPersistentModelIndex &idx : selected) {
- qDebug("change categories on: %s", qUtf8Printable(idx.data().toString()));
+ log::debug("change categories on: {}", idx.data().toString());
QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx);
if (modIdx.row() != m_ContextIdx.row()) {
addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row());
@@ -4216,7 +4315,7 @@ void MainWindow::addRemoveCategories_MenuHandler() {
void MainWindow::replaceCategories_MenuHandler() {
QMenu *menu = qobject_cast<QMenu*>(sender());
if (menu == nullptr) {
- qCritical("not a menu?");
+ log::error("not a menu?");
return;
}
@@ -4271,10 +4370,10 @@ void MainWindow::saveArchiveList()
}
}
if (archiveFile.commitIfDifferent(m_ArchiveListHash)) {
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName())));
+ log::debug("{} saved", QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()));
}
} else {
- qWarning("archive list not initialised");
+ log::warn("archive list not initialised");
}
}
@@ -4291,7 +4390,7 @@ void MainWindow::checkModsForUpdates()
m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); });
NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else {
- qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.");
+ log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.");
}
}
@@ -4411,7 +4510,7 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu,
categoryBox->setChecked(categoryID == info->getPrimaryCategory());
action->setDefaultWidget(categoryBox);
} catch (const std::exception &e) {
- qCritical("failed to create category checkbox: %s", e.what());
+ log::error("failed to create category checkbox: {}", e.what());
}
action->setData(categoryID);
@@ -4423,7 +4522,7 @@ void MainWindow::addPrimaryCategoryCandidates()
{
QMenu *menu = qobject_cast<QMenu*>(sender());
if (menu == nullptr) {
- qCritical("not a menu?");
+ log::error("not a menu?");
return;
}
menu->clear();
@@ -5188,13 +5287,12 @@ void MainWindow::on_actionSettings_triggered()
m_statusBar->checkSettings(m_OrganizerCore.settings());
updateDownloadView();
- m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist());
+ m_OrganizerCore.setLogLevel(settings.logLevel());
m_OrganizerCore.cycleDiagnostics();
toggleMO2EndorseState();
}
-
void MainWindow::on_actionNexus_triggered()
{
const IPluginGame *game = m_OrganizerCore.managedGame();
@@ -5217,7 +5315,7 @@ void MainWindow::installTranslator(const QString &name)
QString fileName = name + "_" + m_CurrentLanguage;
if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) {
if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) {
- qDebug("localization file %s not found", qUtf8Printable(fileName));
+ log::debug("localization file %s not found", fileName);
} // we don't actually expect localization files for English (en, en-us, en-uk, and any variation thereof)
}
@@ -5242,7 +5340,7 @@ void MainWindow::languageChange(const QString &newLanguage)
installTranslator(QFileInfo(fileName).baseName());
}
ui->retranslateUi(this);
- qDebug("loaded language %s", qUtf8Printable(newLanguage));
+ log::debug("loaded language {}", newLanguage);
ui->profileBox->setItemText(0, QObject::tr("<Manage...>"));
@@ -5487,7 +5585,7 @@ void MainWindow::openDataOriginExplorer_clicked()
const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString();
- qDebug().nospace() << "opening in explorer: " << fullPath;
+ log::debug("opening in explorer: {}", fullPath);
shell::ExploreFile(fullPath);
}
@@ -5661,7 +5759,7 @@ void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); });
NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else
- qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.");
+ log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.");
}
}
@@ -5766,7 +5864,7 @@ void MainWindow::finishUpdateInfo()
auto finalMods = watcher->result();
if (finalMods.empty()) {
- qInfo("None of your mods appear to have had recent file updates.");
+ log::info("None of your mods appear to have had recent file updates.");
}
std::set<std::pair<QString, int>> organizedGames;
@@ -5777,7 +5875,7 @@ void MainWindow::finishUpdateInfo()
}
if (!finalMods.empty() && organizedGames.empty())
- qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.");
+ log::warn("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.");
for (auto game : organizedGames)
NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString());
@@ -5920,7 +6018,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa
toggleMO2EndorseState();
if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) {
- qCritical("failed to disconnect endorsement slot");
+ log::error("failed to disconnect endorsement slot");
}
}
@@ -5973,7 +6071,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat
void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString)
{
if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) {
- qDebug(qUtf8Printable(tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)));
+ log::debug("{}", tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID));
} else {
MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this);
}
@@ -6227,9 +6325,7 @@ void MainWindow::removeFromToolbar()
auto itor = list.find(title);
if (itor == list.end()) {
- qWarning().nospace()
- << "removeFromToolbar(): executable '" << title << "' not found";
-
+ log::warn("removeFromToolbar(): executable '{}' not found", title);
return;
}
@@ -6382,11 +6478,11 @@ void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite)
secAttributes.lpSecurityDescriptor = nullptr;
if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) {
- qCritical("failed to create stdout reroute");
+ log::error("failed to create stdout reroute");
}
if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) {
- qCritical("failed to correctly set up the stdout reroute");
+ log::error("failed to correctly set up the stdout reroute");
*stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE;
}
}
@@ -6429,7 +6525,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe
if (progidx != std::string::npos) {
dialog.setLabelText(line.substr(progidx + 11).c_str());
} else if (erroridx != std::string::npos) {
- qWarning("%s", line.c_str());
+ log::warn("{}", line);
errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n");
} else {
std::smatch match;
@@ -6442,7 +6538,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe
std::string dependency(match[2].first, match[2].second);
m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str()));
} else {
- qDebug("[loot] %s", line.c_str());
+ log::debug("[loot] {}", line);
}
}
}
@@ -6487,7 +6583,7 @@ void MainWindow::on_bossButton_clicked()
try {
m_OrganizerCore.prepareVFS();
} catch (const UsvfsConnectorException &e) {
- qDebug(e.what());
+ log::debug("{}", e.what());
return;
} catch (const std::exception &e) {
QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what());
@@ -6517,7 +6613,7 @@ void MainWindow::on_bossButton_clicked()
if (isJobHandle) {
if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) {
if (info.NumberOfProcessIdsInList == 0) {
- qDebug("no more processes in job");
+ log::debug("no more processes in job");
break;
} else {
if (lastProcessID != info.ProcessIdList[0]) {
@@ -6683,8 +6779,13 @@ void MainWindow::on_restoreButton_clicked()
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())));
+
+ const auto e = GetLastError();
+
+ QMessageBox::critical(
+ this, tr("Restore failed"),
+ tr("Failed to restore the backup. Errorcode: %1")
+ .arg(QString::fromStdWString(formatSystemMessage(e))));
}
m_OrganizerCore.refreshESPList(true);
}
@@ -6705,25 +6806,16 @@ void MainWindow::on_restoreModsButton_clicked()
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())));
+ const auto e = GetLastError();
+ QMessageBox::critical(
+ this, tr("Restore failed"),
+ tr("Failed to restore the backup. Errorcode: %1")
+ .arg(formatSystemMessage(e)));
}
m_OrganizerCore.refreshModList(false);
}
}
-void MainWindow::on_actionCopy_Log_to_Clipboard_triggered()
-{
- QStringList lines;
- QAbstractItemModel *model = ui->logList->model();
- for (int i = 0; i < model->rowCount(); ++i) {
- lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString())
- .arg(model->index(i, 1).data(Qt::UserRole).toString())
- .arg(model->index(i, 1).data().toString()));
- }
- QApplication::clipboard()->setText(lines.join("\n"));
-}
-
void MainWindow::on_categoriesAndBtn_toggled(bool checked)
{
if (checked) {
@@ -6794,7 +6886,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m
{
QFileInfo file(url.toLocalFile());
if (!file.exists()) {
- qWarning("invalid source file: %s", qUtf8Printable(file.absoluteFilePath()));
+ log::warn("invalid source file: {}", file.absoluteFilePath());
return;
}
QString target = outputDir + "/" + file.fileName();
@@ -6827,7 +6919,8 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m
success = shellCopy(file.absoluteFilePath(), target, true, this);
}
if (!success) {
- qCritical("file operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError())));
+ const auto e = GetLastError();
+ log::error("file operation failed: {}", formatSystemMessage(e));
}
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 80508787..aa49205d 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -30,6 +30,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modlistsortproxy.h"
#include "savegameinfo.h"
#include "tutorialcontrol.h"
+#include "plugincontainer.h" //class PluginContainer;
+#include "iplugingame.h" //namespace MOBase { class IPluginGame; }
+#include <log.h>
//Note the commented headers here can be replaced with forward references,
//when I get round to cleaning up main.cpp
@@ -38,10 +41,10 @@ class CategoryFactory;
class LockedDialogBase;
class OrganizerCore;
class StatusBar;
-#include "plugincontainer.h" //class PluginContainer;
+
class PluginListSortProxy;
namespace BSA { class Archive; }
-#include "iplugingame.h" //namespace MOBase { class IPluginGame; }
+
namespace MOBase { class IPluginModPage; }
namespace MOBase { class IPluginTool; }
namespace MOBase { class ISaveGame; }
@@ -219,7 +222,9 @@ private:
void updatePinnedExecutables();
void setToolbarSize(const QSize& s);
void setToolbarButtonStyle(Qt::ToolButtonStyle s);
- void toolbarMenu_aboutToShow();
+
+ void updateToolbarMenu();
+ void updateViewMenu();
QMenu* createPopupMenu() override;
void activateSelectedProfile();
@@ -654,6 +659,7 @@ private slots: // ui slots
void on_actionToolBarIconsOnly_triggered();
void on_actionToolBarTextOnly_triggered();
void on_actionToolBarIconsAndText_triggered();
+ void on_actionViewLog_triggered();
void on_centralWidget_customContextMenuRequested(const QPoint &pos);
void on_bsaList_customContextMenuRequested(const QPoint &pos);
@@ -687,7 +693,6 @@ private slots: // ui slots
void on_restoreButton_clicked();
void on_restoreModsButton_clicked();
void on_saveModsButton_clicked();
- void on_actionCopy_Log_to_Clipboard_triggered();
void on_categoriesAndBtn_toggled(bool checked);
void on_categoriesOrBtn_toggled(bool checked);
void on_managedArchiveLabel_linkHovered(const QString &link);
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 70d1cf39..6c6d0bca 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -1286,23 +1286,6 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</widget>
- <widget class="QTreeView" name="logList">
- <property name="contextMenuPolicy">
- <enum>Qt::ActionsContextMenu</enum>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::NoSelection</enum>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="itemsExpandable">
- <bool>false</bool>
- </property>
- <property name="headerHidden">
- <bool>true</bool>
- </property>
- </widget>
</widget>
</item>
</layout>
@@ -1403,6 +1386,7 @@ p, li { white-space: pre-wrap; }
<addaction name="actionToolBarIconsAndText"/>
</widget>
<addaction name="menuToolbars"/>
+ <addaction name="actionViewLog"/>
<addaction name="separator"/>
<addaction name="actionNotifications"/>
</widget>
@@ -1417,6 +1401,49 @@ p, li { white-space: pre-wrap; }
<addaction name="menuRun"/>
<addaction name="menuHelp"/>
</widget>
+ <widget class="QDockWidget" name="logDock">
+ <property name="features">
+ <set>QDockWidget::AllDockWidgetFeatures</set>
+ </property>
+ <property name="windowTitle">
+ <string>Log</string>
+ </property>
+ <attribute name="dockWidgetArea">
+ <number>8</number>
+ </attribute>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="LogList" name="logList">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
<action name="actionInstallMod">
<property name="icon">
<iconset resource="resources.qrc">
@@ -1618,20 +1645,6 @@ p, li { white-space: pre-wrap; }
<string>Endorse Mod Organizer</string>
</property>
</action>
- <action name="actionCopy_Log_to_Clipboard">
- <property name="text">
- <string>Copy &amp;Log</string>
- </property>
- <property name="iconText">
- <string>Copy &amp;Log</string>
- </property>
- <property name="toolTip">
- <string>Copy log to clipboard</string>
- </property>
- <property name="statusTip">
- <string>Copy log to clipboard</string>
- </property>
- </action>
<action name="actionChange_Game">
<property name="icon">
<iconset resource="resources.qrc">
@@ -1736,6 +1749,14 @@ p, li { white-space: pre-wrap; }
<string>Status &amp;bar</string>
</property>
</action>
+ <action name="actionViewLog">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Log</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@@ -1769,6 +1790,11 @@ p, li { white-space: pre-wrap; }
<extends>QWidget</extends>
<header>sortabletreewidget.h</header>
</customwidget>
+ <customwidget>
+ <class>LogList</class>
+ <extends>QTreeView</extends>
+ <header>loglist.h</header>
+ </customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp
index 6c6de3e7..78a5dd4d 100644
--- a/src/messagedialog.cpp
+++ b/src/messagedialog.cpp
@@ -19,10 +19,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "messagedialog.h"
#include "ui_messagedialog.h"
+#include <log.h>
#include <QTimer>
#include <QResizeEvent>
#include <Windows.h>
+using namespace MOBase;
+
MessageDialog::MessageDialog(const QString &text, QWidget *reference) :
QDialog(reference),
ui(new Ui::MessageDialog)
@@ -81,7 +84,8 @@ void MessageDialog::resizeEvent(QResizeEvent *event)
void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront)
{
- qDebug("%s", qUtf8Printable(text));
+ log::debug("{}", text);
+
if (reference != nullptr) {
if (bringToFront || (qApp->activeWindow() != nullptr)) {
MessageDialog *dialog = new MessageDialog(text, reference);
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index e07db437..79e931fb 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "moapplication.h"
#include <report.h>
#include <utility.h>
+#include <log.h>
#include <appconfig.h>
#include <QFile>
#include <QStringList>
@@ -36,7 +37,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDebug>
-using MOBase::reportError;
+using namespace MOBase;
class ProxyStyle : public QProxyStyle {
@@ -114,13 +115,15 @@ 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());
+ log::error(
+ "uncaught exception in handler (object {}, eventtype {}): {}",
+ receiver->objectName(), event->type(), e.what());
reportError(tr("an error occurred: %1").arg(e.what()));
return false;
} catch (...) {
- qCritical("uncaught non-std exception in handler (object %s, eventtype %d)",
- receiver->objectName().toUtf8().constData(), event->type());
+ log::error(
+ "uncaught non-std exception in handler (object {}, eventtype {})",
+ receiver->objectName(), event->type());
reportError(tr("an error occurred"));
return false;
}
@@ -137,7 +140,7 @@ void MOApplication::updateStyle(const QString &fileName)
if (QFile::exists(fileName)) {
setStyleSheet(QString("file:///%1").arg(fileName));
} else {
- qWarning("invalid stylesheet: %s", qUtf8Printable(fileName));
+ log::warn("invalid stylesheet: {}", fileName);
}
}
}
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp
index c3142962..7110a590 100644
--- a/src/modflagicondelegate.cpp
+++ b/src/modflagicondelegate.cpp
@@ -1,6 +1,8 @@
#include "modflagicondelegate.h"
+#include <log.h>
#include <QList>
+using namespace MOBase;
ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED
, ModInfo::FLAG_CONFLICT_OVERWRITE
@@ -117,7 +119,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const
case ModInfo::FLAG_PLUGIN_SELECTED: return QString();
case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked");
default:
- qWarning("ModInfo flag %d has no defined icon", flag);
+ log::warn("ModInfo flag {} has no defined icon", flag);
return QString();
}
}
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 3484b644..5a05e7ca 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -37,6 +37,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <appconfig.h>
#include <scriptextender.h>
#include <unmanagedmods.h>
+#include <log.h>
#include <QApplication>
#include <QDirIterator>
@@ -319,13 +320,12 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei
}
if (organizedGames.empty()) {
- qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.");
+ log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."));
updatesAvailable = false;
} else {
- qInfo() << tr(
+ log::info("{}", tr(
"You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. "
- "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods."
- );
+ "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods."));
}
for (auto game : organizedGames)
@@ -412,7 +412,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei
});
if (mods.size()) {
- qInfo("Checking updates for %d mods...", mods.size());
+ log::info("Checking updates for {} mods...", mods.size());
for (auto mod : mods) {
organizedGames.insert(std::make_pair<QString, int>(mod->getGameName().toLower(), mod->getNexusID()));
@@ -422,7 +422,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei
NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString());
}
} else {
- qInfo("None of the selected mods can be updated.");
+ log::info("None of the selected mods can be updated.");
}
}
@@ -530,10 +530,7 @@ QUrl ModInfo::parseCustomURL() const
const auto url = QUrl::fromUserInput(getCustomURL());
if (!url.isValid()) {
- qCritical()
- << "mod '" << name() << "' has an invalid custom url "
- << "'" << getCustomURL() << "'";
-
+ log::error("mod '{}' has an invalid custom url '{}'", name(), getCustomURL());
return {};
}
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index 47ac84be..4b1e2f76 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -176,7 +176,7 @@ void ModInfoDialog::createTabs()
// check for tabs in the ui not having a corresponding tab in the list
int count = ui->tabWidget->count();
if (count < 0 || count > static_cast<int>(m_tabs.size())) {
- qCritical() << "mod info dialog has more tabs than expected";
+ log::error("mod info dialog has more tabs than expected");
count = static_cast<int>(m_tabs.size());
}
@@ -239,13 +239,13 @@ void ModInfoDialog::setMod(const QString& name)
{
unsigned int index = ModInfo::getIndex(name);
if (index == UINT_MAX) {
- qCritical() << "failed to resolve mod name " << name;
+ log::error("failed to resolve mod name {}", name);
return;
}
auto mod = ModInfo::getByIndex(index);
if (!mod) {
- qCritical() << "mod by index " << index << " is null";
+ log::error("mod by index {} is null", index);
return;
}
@@ -307,7 +307,7 @@ void ModInfoDialog::update(bool firstTime)
// changed
tabInfo->tab->activated();
} else {
- qCritical() << "tab index " << oldTab << " not found";
+ log::error("tab index {} not found", oldTab);
}
}
}
@@ -400,7 +400,7 @@ void ModInfoDialog::reAddTabs(
if (itor == orderedNames.end()) {
// this shouldn't happen, it means there's a tab in the UI that's no
// in the list
- qCritical() << "can't sort tabs, '" << objectName << "' not found";
+ log::error("can't sort tabs, '{}' not found", objectName);
canSort = false;
}
}
@@ -556,9 +556,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id)
}
// this could happen if the tab is not visible right now
- qDebug()
- << "can't switch to tab ID " << static_cast<int>(id)
- << ", not available";
+ log::debug("can't switch to tab ID {}, not available", static_cast<int>(id));
}
MOShared::FilesOrigin* ModInfoDialog::getOrigin()
@@ -753,7 +751,7 @@ void ModInfoDialog::onTabMoved()
}
if (!found) {
- qCritical() << "unknown tab at index " << i;
+ log::error("unknown tab at index {}", i);
}
}
}
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 698c8534..03b490a2 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -364,7 +364,7 @@ void for_each_in_selection(QTreeView* tree, F&& f)
const auto* model = dynamic_cast<ConflictListModel*>(tree->model());
if (!model) {
- qCritical() << "tree doesn't have a ConflictListModel";
+ log::error("tree doesn't have a ConflictListModel");
return;
}
@@ -437,10 +437,18 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
const auto n = smallSelectionSize(tree);
- qDebug().nospace().noquote()
- << (visible ? "unhiding" : "hiding") << " "
- << (n > max_small_selection ? "a lot of" : QString("%1").arg(n))
- << " conflict files";
+ // logging
+ {
+ const QString action = (visible ? "unhiding" : "hiding");
+
+ QString files;
+ if (n > max_small_selection)
+ files = "a lot of";
+ else
+ files = QString("%1").arg(n);
+
+ log::debug("{} {} conflict files", action, files);
+ }
QFlags<FileRenamer::RenameFlags> flags =
(visible ? FileRenamer::UNHIDE : FileRenamer::HIDE);
@@ -453,7 +461,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
auto* model = dynamic_cast<ConflictListModel*>(tree->model());
if (!model) {
- qCritical() << "list doesn't have a ConflictListModel";
+ log::error("list doesn't have a ConflictListModel");
return;
}
@@ -466,7 +474,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
if (visible) {
if (!item->canUnhide()) {
- qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping";
+ log::debug("cannot unhide {}, skipping", item->relativeName());
return true;
}
@@ -474,7 +482,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
} else {
if (!item->canHide()) {
- qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping";
+ log::debug("cannot hide {}, skipping", item->relativeName());
return true;
}
@@ -503,10 +511,10 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible)
return true;
});
- qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done";
+ log::debug("{} conflict files done", (visible ? "unhiding" : "hiding"));
if (changed) {
- qDebug().nospace() << "triggering refresh";
+ log::debug("triggering refresh");
if (origin()) {
emitOriginModified();
@@ -632,7 +640,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree)
const auto* model = dynamic_cast<ConflictListModel*>(tree->model());
if (!model) {
- qCritical() << "tree doesn't have a ConflictListModel";
+ log::error("tree doesn't have a ConflictListModel");
return {};
}
diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp
index fba5d39a..3130b4bd 100644
--- a/src/modinfodialogesps.cpp
+++ b/src/modinfodialogesps.cpp
@@ -3,8 +3,9 @@
#include "modinfodialog.h"
#include "settings.h"
#include <report.h>
+#include <log.h>
-using MOBase::reportError;
+using namespace MOBase;
class ESPItem
{
@@ -297,7 +298,7 @@ void ESPsTab::onActivate()
}
if (esp->isActive()) {
- qWarning("ESPsTab::onActive(): item is already active");
+ log::warn("ESPsTab::onActive(): item is already active");
return;
}
@@ -348,7 +349,7 @@ void ESPsTab::onDeactivate()
}
if (!esp->isActive()) {
- qWarning("ESPsTab::onDeactivate(): item is already inactive");
+ log::warn("ESPsTab::onDeactivate(): item is already inactive");
return;
}
diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp
index 0b519932..207c792d 100644
--- a/src/modinfodialogfiletree.cpp
+++ b/src/modinfodialogfiletree.cpp
@@ -5,8 +5,9 @@
#include "filerenamer.h"
#include <utility.h>
#include <report.h>
+#include <log.h>
-using MOBase::reportError;
+using namespace MOBase;
namespace shell = MOBase::shell;
// if there are more than 50 selected items in the filetree, don't bother
@@ -230,19 +231,19 @@ bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent)
if (m_fs->isDir(index)) {
if (!deleteFileRecursive(index)) {
- qCritical() << "failed to delete" << m_fs->fileName(index);
+ log::error("failed to delete {}", m_fs->fileName(index));
return false;
}
} else {
if (!m_fs->remove(index)) {
- qCritical() << "failed to delete", m_fs->fileName(index);
+ log::error("failed to delete {}", m_fs->fileName(index));
return false;
}
}
}
if (!m_fs->remove(parent)) {
- qCritical() << "failed to delete" << m_fs->fileName(parent);
+ log::error("failed to delete {}", m_fs->fileName(parent));
return false;
}
@@ -256,9 +257,9 @@ void FileTreeTab::changeVisibility(bool visible)
bool changed = false;
bool stop = false;
- qDebug().nospace()
- << (visible ? "unhiding" : "hiding") << " "
- << selection.size() << " filetree files";
+ log::debug(
+ "{} {} filetree files",
+ (visible ? "unhiding" : "hiding"), selection.size());
QFlags<FileRenamer::RenameFlags> flags =
(visible ? FileRenamer::UNHIDE : FileRenamer::HIDE);
@@ -279,13 +280,13 @@ void FileTreeTab::changeVisibility(bool visible)
if (visible) {
if (!canUnhideFile(false, path)) {
- qDebug().nospace() << "cannot unhide " << path << ", skipping";
+ log::debug("cannot unhide {}, skipping", path);
continue;
}
result = unhideFile(renamer, path);
} else {
if (!canHideFile(false, path)) {
- qDebug().nospace() << "cannot hide " << path << ", skipping";
+ log::debug("cannot hide {}, skipping", path);
continue;
}
result = hideFile(renamer, path);
@@ -311,7 +312,7 @@ void FileTreeTab::changeVisibility(bool visible)
}
}
- qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done";
+ log::debug("{} filetree files done", (visible ? "unhiding" : "hiding"));
if (changed) {
if (origin()) {
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index 69866902..10362058 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -2,7 +2,9 @@
#include "ui_modinfodialog.h"
#include "settings.h"
#include "utility.h"
+#include <log.h>
+using namespace MOBase;
using namespace ImagesTabHelpers;
QSize resizeWithAspectRatio(const QSize& original, const QSize& available)
@@ -896,10 +898,9 @@ void File::ensureOriginalLoaded()
QImageReader reader(m_path);
if (!reader.read(&m_original)) {
- qCritical().noquote().nospace()
- << "failed to load '" << m_path << "'\n"
- << reader.errorString() << " "
- << "(error " << static_cast<int>(reader.error()) << ")";
+ log::error(
+ "failed to load '{}'\n{} (error {})",
+ m_path, reader.errorString(), static_cast<int>(reader.error()));
m_failed = true;
}
diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp
index e606525c..6d28cbe3 100644
--- a/src/modinfodialognexus.cpp
+++ b/src/modinfodialognexus.cpp
@@ -6,8 +6,9 @@
#include "bbcode.h"
#include <versioninfo.h>
#include <utility.h>
+#include <log.h>
-namespace shell = MOBase::shell;
+using namespace MOBase;
bool isValidModID(int id)
{
@@ -350,7 +351,7 @@ void NexusTab::onRefreshBrowser()
mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0));
updateWebpage();
} else {
- qInfo("Mod has no valid Nexus ID, info can't be updated.");
+ log::info("Mod has no valid Nexus ID, info can't be updated.");
}
}
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index 12137bcb..ce29e11e 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -68,8 +68,7 @@ ModInfoRegular::~ModInfoRegular()
try {
saveMeta();
} catch (const std::exception &e) {
- qCritical("failed to save meta information for \"%s\": %s",
- qUtf8Printable(m_Name), e.what());
+ log::error("failed to save meta information for \"{}\": {}", m_Name, e.what());
}
}
@@ -258,14 +257,14 @@ void ModInfoRegular::saveMeta()
if (metaFile.status() == QSettings::NoError) {
m_MetaInfoChanged = false;
} else {
- qCritical()
- << QString("failed to write %1/meta.ini: error %2")
- .arg(absolutePath()).arg(metaFile.status());
+ log::error(
+ "failed to write {}/meta.ini: error {}",
+ absolutePath(), metaFile.status());
}
} else {
- qCritical()
- << QString("failed to write %1/meta.ini: error %2")
- .arg(absolutePath()).arg(metaFile.status());
+ log::error(
+ "failed to write {}/meta.ini: error {}",
+ absolutePath(), metaFile.status());
}
}
}
@@ -425,14 +424,13 @@ bool ModInfoRegular::setName(const QString &name)
return false;
}
if (!modDir.rename(tempName, name)) {
- qCritical("rename to final name failed after successful rename to intermediate name");
+ log::error("rename to final name failed after successful rename to intermediate name");
modDir.rename(tempName, m_Name);
return false;
}
} else {
if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) {
- qCritical("failed to rename mod %s (errorcode %d)",
- qUtf8Printable(name), ::GetLastError());
+ log::error("failed to rename mod {} (errorcode {})", name, ::GetLastError());
return false;
}
}
@@ -885,8 +883,9 @@ std::vector<QString> ModInfoRegular::getIniTweaks() const
int numTweaks = metaFile.beginReadArray("INI Tweaks");
if (numTweaks != 0) {
- qDebug("%d active ini tweaks in %s",
- numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData());
+ log::debug(
+ "{} active ini tweaks in {}",
+ numTweaks, QDir::toNativeSeparators(metaFileName));
}
for (int i = 0; i < numTweaks; ++i) {
diff --git a/src/modlist.cpp b/src/modlist.cpp
index 1710a98d..c591c49b 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -271,11 +271,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
int categoryIdx = categoryFactory.getCategoryIndex(category);
return categoryFactory.getCategoryName(categoryIdx);
} catch (const std::exception &e) {
- qCritical("failed to retrieve category name: %s", e.what());
+ log::error("failed to retrieve category name: {}", e.what());
return QString();
}
} else {
- qWarning("category %d doesn't exist (may have been removed)", category);
+ log::warn("category {} doesn't exist (may have been removed)", category);
modInfo->setCategory(category, false);
return QString();
}
@@ -449,7 +449,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
try {
return modInfo->getDescription();
} catch (const std::exception &e) {
- qCritical("invalid mod description: %s", e.what());
+ log::error("invalid mod description: {}", e.what());
return QString();
}
} else if (column == COL_VERSION) {
@@ -488,7 +488,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
try {
categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
} catch (const std::exception &e) {
- qCritical("failed to generate tooltip: %s", e.what());
+ log::error("failed to generate tooltip: {}", e.what());
return QString();
}
}
@@ -618,8 +618,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
result = true;
} break;
default: {
- qWarning("edit on column \"%s\" not supported",
- getColumnName(index.column()).toUtf8().constData());
+ log::warn(
+ "edit on column \"{}\" not supported",
+ getColumnName(index.column()).toUtf8().constData());
result = false;
} break;
}
@@ -635,9 +636,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
try {
m_ModStateChanged(info->name(), newState);
} catch (const std::exception &e) {
- qCritical("failed to invoke state changed notification: %s", e.what());
+ log::error("failed to invoke state changed notification: {}", e.what());
} catch (...) {
- qCritical("failed to invoke state changed notification: unknown exception");
+ log::error("failed to invoke state changed notification: unknown exception");
}
}
@@ -833,7 +834,7 @@ void ModList::modInfoChanged(ModInfo::Ptr info)
emit dataChanged(index(row, 0), index(row, columnCount()));
emit postDataChanged();
} else {
- qCritical("modInfoChanged not called after modInfoAboutToChange");
+ log::error("modInfoChanged not called after modInfoAboutToChange");
}
m_ChangeInfo.name = QString();
}
@@ -1012,9 +1013,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
for (auto url : mimeData->urls()) {
- //qDebug("URL drop requested: %s -> %s", qUtf8Printable(url.url()), qUtf8Printable(modDir.canonicalPath()));
if (!url.isLocalFile()) {
- qDebug("URL drop ignored: \"%s\" is not a local file", qUtf8Printable(url.url()));
+ log::debug("URL drop ignored: \"{}\" is not a local file", url.url());
continue;
}
@@ -1034,7 +1034,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
originName = overwriteName;
relativePath = overwriteDir.relativeFilePath(sourceFile);
} else {
- qDebug("URL drop ignored: \"%s\" is not a known file to MO", qUtf8Printable(sourceFile));
+ log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile);
continue;
}
@@ -1046,7 +1046,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
if (sourceList.count()) {
if (!shellMove(sourceList, targetList)) {
- qDebug("Failed to move file (error %d)", ::GetLastError());
+ log::debug("Failed to move file (error {})", ::GetLastError());
return false;
}
}
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp
index 2d9ea4a5..77ffad96 100644
--- a/src/modlistsortproxy.cpp
+++ b/src/modlistsortproxy.cpp
@@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "profile.h"
#include "messagedialog.h"
#include "qtgroupingproxy.h"
+#include <log.h>
#include <QMenu>
#include <QCheckBox>
#include <QWidgetAction>
@@ -30,6 +31,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDebug>
#include <QTreeView>
+using namespace MOBase;
ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent)
: QSortFilterProxyModel(parent)
@@ -194,7 +196,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory()));
lt = leftCatName < rightCatName;
} catch (const std::exception &e) {
- qCritical("failed to compare categories: %s", e.what());
+ log::error("failed to compare categories: {}", e.what());
}
}
}
@@ -240,7 +242,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
// nop, already compared by priority
} break;
default: {
- qWarning() << "Sorting is not defined for column " << left.column();
+ log::warn("Sorting is not defined for column {}", left.column());
} break;
}
return lt;
@@ -474,13 +476,13 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons
}
if (row >= static_cast<int>(m_Profile->numMods())) {
- qWarning("invalid row index: %d", row);
+ log::warn("invalid row index: {}", row);
return false;
}
QModelIndex idx = sourceModel()->index(row, 0, parent);
if (!idx.isValid()) {
- qDebug("invalid mod index");
+ log::debug("invalid mod index");
return false;
}
if (sourceModel()->hasChildren(idx)) {
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index 2bcd72f3..0e2bb45b 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "bbcode.h"
#include <utility.h>
#include <util.h>
+#include <log.h>
#include <QApplication>
#include <QNetworkCookieJar>
@@ -40,12 +41,11 @@ using namespace MOShared;
void throttledWarning(const APIUserAccount& user)
{
- qCritical() <<
- QString(
- "You have fewer than %1 requests remaining (%2). Only downloads and "
- "login validation are being allowed.")
- .arg(APIUserAccount::ThrottleThreshold)
- .arg(user.remainingRequests());
+ log::error(
+ "You have fewer than {} requests remaining ({}). Only downloads and "
+ "login validation are being allowed.",
+ APIUserAccount::ThrottleThreshold,
+ user.remainingRequests());
}
@@ -316,15 +316,15 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo
} else {
modID = strtol(candidate.c_str(), nullptr, 10);
}
- qDebug("mod id guessed: %s -> %d", qUtf8Printable(fileName), modID);
+ log::debug("mod id guessed: {} -> {}", fileName, modID);
} else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) {
- qDebug("simple expression matched, using name only");
+ log::debug("simple expression matched, using name only");
modName = QString::fromUtf8(result[1].str().c_str());
modName = modName.replace('_', ' ').trimmed();
modID = -1;
} else {
- qDebug("no expression matched!");
+ log::debug("no expression matched!");
modName.clear();
modID = -1;
}
@@ -343,7 +343,7 @@ QString NexusInterface::getGameURL(QString gameName) const
if (game != nullptr) {
return "https://www.nexusmods.com/" + game->gameNexusName().toLower();
} else {
- qCritical("getGameURL can't find plugin for %s", qUtf8Printable(gameName));
+ log::error("getGameURL can't find plugin for {}", gameName);
return "";
}
}
@@ -354,7 +354,7 @@ QString NexusInterface::getOldModsURL(QString gameName) const
if (game != nullptr) {
return "https://" + game->gameNexusName().toLower() + ".nexusmods.com/mods";
} else {
- qCritical("getOldModsURL can't find plugin for %s", qUtf8Printable(gameName));
+ log::error("getOldModsURL can't find plugin for {}", gameName);
return "";
}
}
@@ -463,7 +463,7 @@ int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant
IPluginGame *game = getGame(gameName);
if (game == nullptr) {
- qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName));
+ log::error("requestUpdates can't find plugin for {}", gameName);
return -1;
}
@@ -520,7 +520,7 @@ int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, QOb
{
IPluginGame *gamePlugin = getGame(gameName);
if (gamePlugin == nullptr) {
- qCritical("requestFileInfo can't find plugin for %s", qUtf8Printable(gameName));
+ log::error("requestFileInfo can't find plugin for {}", gameName);
return -1;
}
@@ -686,7 +686,7 @@ void NexusInterface::nextRequest()
} else if (getAccessManager()->validateWaiting()) {
return;
} else {
- qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.");
+ log::error("{}", tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API."));
}
}
@@ -695,9 +695,13 @@ void NexusInterface::nextRequest()
QTime time = QTime::currentTime();
QTime targetTime;
targetTime.setHMS((time.hour() + 1) % 23, 5, 0);
- QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. "
- "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.");
- qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60);
+ QString warning = tr(
+ "You've exceeded the Nexus API rate limit and requests are now being throttled. "
+ "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.")
+ .arg(time.secsTo(targetTime) / 60)
+ .arg(time.secsTo(targetTime) % 60);
+
+ log::warn("{}", warning);
return;
}
@@ -747,8 +751,8 @@ void NexusInterface::nextRequest()
url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6")
.arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires);
} else {
- qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, "
- "or the download link was generated by a different account than the one stored in Mod Organizer.");
+ log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, "
+ "or the download link was generated by a different account than the one stored in Mod Organizer."));
return;
}
} break;
@@ -828,16 +832,16 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
m_User.limits(parseLimits(reply));
if (!m_User.exhausted()) {
- qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team.");
+ log::warn("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team.");
}
else {
- qWarning("All API requests have been consumed and are now being denied.");
+ log::warn("All API requests have been consumed and are now being denied.");
}
emit requestsChanged(getAPIStats(), m_User);
- qWarning("Error: %s", reply->errorString().toUtf8().constData());
+ log::warn("Error: {}", reply->errorString());
} else {
- qWarning("request failed: %s", reply->errorString().toUtf8().constData());
+ log::warn("request failed: {}", reply->errorString());
}
emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), reply->errorString());
} else {
@@ -856,7 +860,7 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
if (nexusError.length() == 0) {
nexusError = tr("empty response");
}
- qDebug("nexus error: %s", qUtf8Printable(nexusError));
+ log::debug("nexus error: {}", nexusError);
emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), nexusError);
} else {
QJsonDocument responseDoc = QJsonDocument::fromJson(data);
@@ -940,14 +944,13 @@ void NexusInterface::requestError(QNetworkReply::NetworkError)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply == nullptr) {
- qWarning("invalid sender type");
+ log::warn("invalid sender type");
return;
}
- qCritical("request (%s) error: %s (%d)",
- qUtf8Printable(reply->url().toString()),
- qUtf8Printable(reply->errorString()),
- reply->error());
+ log::error(
+ "request ({}) error: {} ({})",
+ reply->url().toString(), reply->errorString(), reply->error());
}
@@ -955,7 +958,7 @@ void NexusInterface::requestTimeout()
{
QTimer *timer = qobject_cast<QTimer*>(sender());
if (timer == nullptr) {
- qWarning("invalid sender type");
+ log::warn("invalid sender type");
return;
}
for (std::list<NXMRequestInfo>::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) {
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index c413e156..fd1dc0c1 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -600,9 +600,9 @@ void NXMAccessManager::showCookies() const
{
QUrl url(NexusBaseUrl + "/");
for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) {
- qDebug("%s - %s (expires: %s)",
+ log::debug("{} - {} (expires: {})",
cookie.name().constData(), cookie.value().constData(),
- qUtf8Printable(cookie.expirationDate().toString()));
+ cookie.expirationDate().toString());
}
}
@@ -612,7 +612,7 @@ void NXMAccessManager::clearCookies()
if (jar != nullptr) {
jar->clear();
} else {
- qWarning("failed to clear cookies, invalid cookie jar");
+ log::warn("failed to clear cookies, invalid cookie jar");
}
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index eeb69e61..1e164525 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -14,7 +14,6 @@
#include "plugincontainer.h"
#include "pluginlistsortproxy.h"
#include "profile.h"
-#include "logbuffer.h"
#include "credentialsdialog.h"
#include "filedialogmemory.h"
#include "modinfodialog.h"
@@ -78,26 +77,21 @@ CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
static bool isOnline()
{
- QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
+ const auto runningFlags =
+ QNetworkInterface::IsUp | QNetworkInterface::IsRunning;
- bool connected = false;
- for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected;
- ++iter) {
- if ((iter->flags() & QNetworkInterface::IsUp)
- && (iter->flags() & QNetworkInterface::IsRunning)
- && !(iter->flags() & QNetworkInterface::IsLoopBack)) {
- auto addresses = iter->addressEntries();
- if (addresses.count() == 0) {
- continue;
+ for (auto&& i : QNetworkInterface::allInterfaces()) {
+ if (!(i.flags() & QNetworkInterface::IsLoopBack)) {
+ if (i.flags() & runningFlags) {
+ auto addresses = i.addressEntries();
+ if (!addresses.empty()) {
+ return true;
+ }
}
- qDebug("interface %s seems to be up (address: %s)",
- qUtf8Printable(iter->humanReadableName()),
- qUtf8Printable(addresses[0].ip().toString()));
- connected = true;
}
}
- return connected;
+ return false;
}
static bool renameFile(const QString &oldName, const QString &newName,
@@ -201,49 +195,49 @@ bool checkService()
try {
serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
if (!serviceManagerHandle) {
- qWarning("failed to open service manager (query status) (error %d)", GetLastError());
+ log::warn("failed to open service manager (query status) (error {})", GetLastError());
throw 1;
}
serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
if (!serviceHandle) {
- qWarning("failed to open EventLog service (query status) (error %d)", GetLastError());
+ log::warn("failed to open EventLog service (query status) (error {})", GetLastError());
throw 2;
}
if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
|| (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- qWarning("failed to get size of service config (error %d)", GetLastError());
+ log::warn("failed to get size of service config (error {})", GetLastError());
throw 3;
}
DWORD serviceConfigSize = bytesNeeded;
serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
- qWarning("failed to query service config (error %d)", GetLastError());
+ log::warn("failed to query service config (error {})", GetLastError());
throw 4;
}
if (serviceConfig->dwStartType == SERVICE_DISABLED) {
- qCritical("Windows Event Log service is disabled!");
+ log::error("Windows Event Log service is disabled!");
serviceRunning = false;
}
if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
|| (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- qWarning("failed to get size of service status (error %d)", GetLastError());
+ log::warn("failed to get size of service status (error {})", GetLastError());
throw 5;
}
DWORD serviceStatusSize = bytesNeeded;
serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize);
if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) {
- qWarning("failed to query service status (error %d)", GetLastError());
+ log::warn("failed to query service status (error {})", GetLastError());
throw 6;
}
if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
- qCritical("Windows Event Log service is not running");
+ log::error("Windows Event Log service is not running");
serviceRunning = false;
}
}
@@ -269,12 +263,12 @@ bool checkService()
}
-OrganizerCore::OrganizerCore(const QSettings &initSettings)
+OrganizerCore::OrganizerCore(Settings &settings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
- , m_Settings(initSettings)
+ , m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
, m_AboutToRun()
, m_FinishedRun()
@@ -295,7 +289,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings)
NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory());
- MOBase::QuestionBoxMemory::init(initSettings.fileName());
+ MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName());
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
@@ -342,7 +336,6 @@ OrganizerCore::~OrganizerCore()
m_CurrentProfile = nullptr;
ModInfo::clear();
- LogBuffer::cleanQuit();
m_ModList.setProfile(nullptr);
// NexusInterface::instance()->cleanup();
@@ -356,7 +349,7 @@ QString OrganizerCore::commitSettings(const QString &iniFile)
// make a second attempt using qt functions but if that fails print the
// error from the first attempt
if (!renameFile(iniFile + ".new", iniFile)) {
- return windowsErrorString(err);
+ return QString::fromStdWString(formatSystemMessage(err));
}
}
return QString();
@@ -389,10 +382,12 @@ void OrganizerCore::storeSettings()
+ QString::fromStdWString(AppConfig::iniFileName());
if (QFileInfo(iniFile).exists()) {
if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) {
+ const auto e = GetLastError();
QMessageBox::critical(
qApp->activeWindow(), tr("Failed to write settings"),
tr("An error occurred trying to update MO settings to %1: %2")
- .arg(iniFile, windowsErrorString(::GetLastError())));
+ .arg(iniFile)
+ .arg(QString::fromStdWString(formatSystemMessage(e))));
return;
}
}
@@ -404,8 +399,9 @@ void OrganizerCore::storeSettings()
if (result == QSettings::NoError) {
QString errMsg = commitSettings(iniFile);
if (!errMsg.isEmpty()) {
- qWarning("settings file not writable, may be locked by another "
- "application, trying direct write");
+ log::warn(
+ "settings file not writable, may be locked by another "
+ "application, trying direct write");
writeTarget = iniFile;
result = storeSettings(iniFile);
}
@@ -438,7 +434,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access)
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
lastError = GetLastError();
- qCritical("unable to get snapshot of processes (error %d)", lastError);
+ log::error("unable to get snapshot of processes (error {})", lastError);
return false;
}
@@ -447,7 +443,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access)
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnap, &pe32)) {
lastError = GetLastError();
- qCritical("unable to get first process (error %d)", lastError);
+ log::error("unable to get first process (error {})", lastError);
CloseHandle(hProcessSnap);
return false;
}
@@ -487,7 +483,7 @@ return true;
void OrganizerCore::updateExecutablesList(QSettings &settings)
{
if (m_PluginContainer == nullptr) {
- qCritical("can't update executables list now");
+ log::error("can't update executables list now");
return;
}
@@ -544,7 +540,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface,
if (isOnline() && !m_Settings.offlineMode()) {
m_Updater.testForUpdate();
} else {
- qDebug("user doesn't seem to be connected to the internet");
+ log::debug("user doesn't seem to be connected to the internet");
}
}
}
@@ -606,7 +602,7 @@ bool OrganizerCore::nexusApi(bool retry)
QString apiKey;
if (m_Settings.getNexusApiKey(apiKey)) {
// credentials stored or user entered them manually
- qDebug("attempt to verify nexus api key");
+ log::debug("attempt to verify nexus api key");
accessManager->apiCheck(apiKey);
return true;
} else {
@@ -628,7 +624,7 @@ void OrganizerCore::startMOUpdate()
void OrganizerCore::downloadRequestedNXM(const QString &url)
{
- qDebug("download requested: %s", qUtf8Printable(url));
+ log::debug("download requested: {}", url);
if (nexusApi()) {
m_PendingDownloads.append(url);
} else {
@@ -658,7 +654,7 @@ void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, in
}
} catch (const std::exception &e) {
MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow());
- qCritical("exception starting download: %s", e.what());
+ log::error("exception starting download: {}", e.what());
}
}
@@ -728,11 +724,25 @@ void OrganizerCore::prepareVFS()
m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
}
-void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist) {
+void OrganizerCore::updateVFSParams(
+ log::Levels logLevel, int crashDumpsType, QString executableBlacklist)
+{
setGlobalCrashDumpsType(crashDumpsType);
m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist);
}
+void OrganizerCore::setLogLevel(log::Levels level)
+{
+ m_Settings.setLogLevel(level);
+
+ updateVFSParams(
+ m_Settings.logLevel(),
+ m_Settings.crashDumpsType(),
+ m_Settings.executablesBlacklist());
+
+ log::getDefault().setLevel(m_Settings.logLevel());
+}
+
bool OrganizerCore::cycleDiagnostics() {
if (int maxDumps = settings().crashDumpsMax())
removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
@@ -1207,7 +1217,9 @@ QString OrganizerCore::findJavaInstallation(const QString& jarFile)
if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
DWORD binaryType = 0UL;
if (!::GetBinaryTypeW(buffer, &binaryType)) {
- qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError());
+ log::debug(
+ "failed to determine binary type of \"{}\": {}",
+ QString::fromWCharArray(buffer), ::GetLastError());
} else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) {
return QString::fromWCharArray(buffer);
}
@@ -1381,9 +1393,9 @@ bool OrganizerCore::previewFileWithAlternatives(
// sanity check, this shouldn't happen unless the caller passed an
// incorrect id
- qWarning().nospace()
- << "selected preview origin " << selectedOrigin << " not found in "
- << "list of alternatives";
+ log::warn(
+ "selected preview origin {} not found in list of alternatives",
+ selectedOrigin);
}
for (int id : origins) {
@@ -1458,7 +1470,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary,
// need to remove our stored load order because it may be outdated if a foreign tool changed the
// file time. After removing that file, refreshESPList will use the file time as the order
if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- qDebug("removing loadorder.txt");
+ log::debug("removing loadorder.txt");
QFile::remove(m_CurrentProfile->getLoadOrderFileName());
}
refreshDirectoryStructure();
@@ -1551,7 +1563,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
bool steamFound = true;
bool steamAccess = true;
if (!testForSteam(&steamFound, &steamAccess)) {
- qCritical("unable to determine state of Steam");
+ log::error("unable to determine state of Steam");
}
if (!steamFound) {
@@ -1568,9 +1580,9 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
steamFound = true;
steamAccess = true;
if (!testForSteam(&steamFound, &steamAccess)) {
- qCritical("unable to determine state of Steam");
+ log::error("unable to determine state of Steam");
} else if (!steamFound) {
- qCritical("could not find Steam");
+ log::error("could not find Steam");
}
} else if (result == QDialogButtonBox::Cancel) {
@@ -1591,14 +1603,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
if (result == QDialogButtonBox::Yes) {
WCHAR cwd[MAX_PATH];
if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- qCritical("unable to get current directory (error %d)", GetLastError());
+ log::error("unable to get current directory (error {})", GetLastError());
cwd[0] = L'\0';
}
if (!Helper::adminLaunch(
qApp->applicationDirPath().toStdWString(),
qApp->applicationFilePath().toStdWString(),
std::wstring(cwd))) {
- qCritical("unable to relaunch MO as admin");
+ log::error("unable to relaunch MO as admin");
return INVALID_HANDLE_VALUE;
}
qApp->exit(0);
@@ -1626,7 +1638,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
m_USVFS.updateForcedLibraries(forcedLibraries);
} catch (const UsvfsConnectorException &e) {
- qDebug(e.what());
+ log::debug(e.what());
return INVALID_HANDLE_VALUE;
} catch (const std::exception &e) {
QMessageBox::warning(window, tr("Error"), e.what());
@@ -1693,17 +1705,16 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
.arg(QDir::toNativeSeparators(cwdPath),
QDir::toNativeSeparators(binPath), arguments);
- qDebug() << "Spawning proxyed process <" << cmdline << ">";
+ log::debug("Spawning proxyed process <{}>", cmdline);
return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
cmdline, QCoreApplication::applicationDirPath(), true);
} else {
- qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">";
+ log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
return startBinary(binary, arguments, currentDirectory, true);
}
} else {
- qDebug("start of \"%s\" canceled by plugin",
- qUtf8Printable(binary.absoluteFilePath()));
+ log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
return INVALID_HANDLE_VALUE;
}
}
@@ -1798,8 +1809,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable,
currentDirectory = exe.workingDirectory();
}
} catch (const std::runtime_error &) {
- qWarning("\"%s\" not set up as executable",
- qUtf8Printable(executable));
+ log::warn("\"{}\" not set up as executable", executable);
binary = QFileInfo(executable);
}
}
@@ -1872,16 +1882,18 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL
processName += QString(" (%1)").arg(currentPID);
if (uilock)
uilock->setProcessName(processName);
- qDebug() << "Waiting for"
- << (originalHandle ? "spawned" : "usvfs")
- << "process completion :" << qUtf8Printable(processName);
+
+ log::debug(
+ "Waiting for {} process completion: {}",
+ (originalHandle ? "spawned" : "usvfs"), processName);
+
newHandle = false;
}
// Wait for a an event on the handle, a key press, mouse click or timeout
res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON);
if (res == WAIT_FAILED) {
- qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError();
+ log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError());
break;
}
@@ -1897,7 +1909,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL
if (res == WAIT_OBJECT_0) {
// process we were waiting on has completed
if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode))
- qWarning() << "Failed getting exit code of complete process :" << GetLastError();
+ log::warn("Failed getting exit code of complete process: {}", GetLastError());
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
originalHandle = false;
@@ -1943,11 +1955,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL
}
if (res == WAIT_OBJECT_0)
- qDebug() << "Waiting for process completion successfull";
+ log::debug("Waiting for process completion successfull");
else if (uiunlocked)
- qDebug() << "Waiting for process completion aborted by UI";
+ log::debug("Waiting for process completion aborted by UI");
else
- qDebug() << "Waiting for process completion not successfull :" << res;
+ log::debug("Waiting for process completion not successfull: {}", res);
if (handle != INVALID_HANDLE_VALUE)
::CloseHandle(handle);
@@ -1962,7 +1974,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& hidde
DWORD pids[querySize];
size_t found = querySize;
if (!::GetVFSProcessList(&found, pids)) {
- qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!";
+ log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!");
return INVALID_HANDLE_VALUE;
}
@@ -1974,7 +1986,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& hidde
HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]);
if (handle == INVALID_HANDLE_VALUE) {
- qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError();
+ log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError());
continue;
}
@@ -2118,7 +2130,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
dir.entryList(QStringList() << "*.esm", QDir::Files)) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esm));
+ log::warn("failed to activate {}", esm);
continue;
}
@@ -2134,7 +2146,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
dir.entryList(QStringList() << "*.esl", QDir::Files)) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esl));
+ log::warn("failed to activate {}", esl);
continue;
}
@@ -2150,7 +2162,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
for (const QString &esp : esps) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esp));
+ log::warn("failed to activate {}", esp);
continue;
}
@@ -2558,7 +2570,7 @@ std::vector<unsigned int> OrganizerCore::activeProblems() const
// of a "log spam". But since this is a sevre error which will most likely make the
// game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it
// easier for the user to notice the warning.
- qWarning("hook.dll found in game folder: %s", qUtf8Printable(hookdll));
+ log::warn("hook.dll found in game folder: {}", hookdll);
problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND);
}
return problems;
@@ -2604,7 +2616,7 @@ void OrganizerCore::startGuidedFix(unsigned int) const
bool OrganizerCore::saveCurrentLists()
{
if (m_DirectoryUpdate) {
- qWarning("not saving lists during directory update");
+ log::warn("not saving lists during directory update");
return false;
}
@@ -2698,7 +2710,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
result.reserve(result.size() + saveMap.size());
result.insert(result.end(), saveMap.begin(), saveMap.end());
} else {
- qWarning("local save games not supported by this game plugin");
+ log::warn("local save games not supported by this game plugin");
}
}
diff --git a/src/organizercore.h b/src/organizercore.h
index 99b1c5f2..2aa7e707 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -21,6 +21,7 @@
#include <delayedfilewriter.h>
#include <boost/signals2.hpp>
#include "executableinfo.h"
+#include <log.h>
class ModListSortProxy;
class PluginListSortProxy;
@@ -96,7 +97,7 @@ public:
static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
- OrganizerCore(const QSettings &initSettings);
+ OrganizerCore(Settings &settings);
~OrganizerCore();
@@ -191,7 +192,11 @@ public:
void prepareVFS();
- void updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist);
+ void updateVFSParams(
+ MOBase::log::Levels logLevel, int crashDumpsType,
+ QString executableBlacklist);
+
+ void setLogLevel(MOBase::log::Levels level);
bool cycleDiagnostics();
@@ -333,7 +338,7 @@ private:
Profile *m_CurrentProfile;
- Settings m_Settings;
+ Settings& m_Settings;
SelfUpdater m_Updater;
diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp
index ff4099cf..715e11e3 100644
--- a/src/overwriteinfodialog.cpp
+++ b/src/overwriteinfodialog.cpp
@@ -120,18 +120,18 @@ bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index)
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());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex));
return false;
}
} else {
if (!m_FileSystemModel->remove(childIndex)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex));
return false;
}
}
}
if (!m_FileSystemModel->remove(index)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(index));
return false;
}
return true;
diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp
index 1ed463c6..8657f356 100644
--- a/src/persistentcookiejar.cpp
+++ b/src/persistentcookiejar.cpp
@@ -1,8 +1,10 @@
#include "persistentcookiejar.h"
+#include <log.h>
#include <QTemporaryFile>
#include <QDataStream>
#include <QNetworkCookie>
+using namespace MOBase;
PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent)
: QNetworkCookieJar(parent), m_FileName(fileName)
@@ -11,7 +13,7 @@ PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *paren
}
PersistentCookieJar::~PersistentCookieJar() {
- qDebug("save %s", qUtf8Printable(m_FileName));
+ log::debug("save {}", m_FileName);
save();
}
@@ -24,7 +26,7 @@ void PersistentCookieJar::clear() {
void PersistentCookieJar::save() {
QTemporaryFile file;
if (!file.open()) {
- qCritical("failed to save cookies: couldn't create temporary file");
+ log::error("failed to save cookies: couldn't create temporary file");
return;
}
QDataStream data(&file);
@@ -40,14 +42,14 @@ void PersistentCookieJar::save() {
QFile oldCookies(m_FileName);
if (oldCookies.exists()) {
if (!oldCookies.remove()) {
- qCritical("failed to save cookies: failed to remove %s", qUtf8Printable(m_FileName));
+ log::error("failed to save cookies: failed to remove {}", m_FileName);
return;
}
} // if it doesn't exists that's fine
}
if (!file.copy(m_FileName)) {
- qCritical("failed to save cookies: failed to write %s", qUtf8Printable(m_FileName));
+ log::error("failed to save cookies: failed to write {}", m_FileName);
}
}
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 2126c5ef..62cdff1e 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -69,7 +69,7 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin)
if (plugin == nullptr) {
return false;
} else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin->name()))) {
- qWarning("plugin failed to initialize");
+ log::warn("plugin failed to initialize");
return false;
}
return true;
@@ -91,7 +91,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
{ // generic treatment for all plugins
IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin);
if (pluginObj == nullptr) {
- qDebug("not an IPlugin");
+ log::debug("not an IPlugin");
return false;
}
plugin->setProperty("filename", fileName);
@@ -164,12 +164,13 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
for (QObject *proxiedPlugin : matchingPlugins) {
if (proxiedPlugin != nullptr) {
if (registerPlugin(proxiedPlugin, pluginName)) {
- qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName()));
+ log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName());
}
else {
- qWarning("plugin \"%s\" failed to load. If this plugin is for an older version of MO "
- "you have to update it or delete it if no update exists.",
- qUtf8Printable(pluginName));
+ log::warn(
+ "plugin \"{}\" failed to load. If this plugin is for an older version of MO "
+ "you have to update it or delete it if no update exists.",
+ pluginName);
}
}
}
@@ -190,7 +191,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName)
}
}
- qDebug("no matching plugin interface");
+ log::debug("no matching plugin interface");
return false;
}
@@ -224,7 +225,7 @@ void PluginContainer::unloadPlugins()
QPluginLoader *loader = m_PluginLoaders.back();
m_PluginLoaders.pop_back();
if ((loader != nullptr) && !loader->unload()) {
- qDebug("failed to unload %s: %s", qUtf8Printable(loader->fileName()), qUtf8Printable(loader->errorString()));
+ log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString());
}
delete loader;
}
@@ -273,13 +274,13 @@ void PluginContainer::loadPlugins()
loadCheck.open(QIODevice::WriteOnly);
QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath());
- qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData());
+ log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath));
QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot);
while (iter.hasNext()) {
iter.next();
if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) {
- qDebug("plugin \"%s\" blacklisted", qUtf8Printable(iter.fileName()));
+ log::debug("plugin \"{}\" blacklisted", iter.fileName());
continue;
}
loadCheck.write(iter.fileName().toUtf8());
@@ -290,15 +291,16 @@ void PluginContainer::loadPlugins()
std::unique_ptr<QPluginLoader> pluginLoader(new QPluginLoader(pluginName, this));
if (pluginLoader->instance() == nullptr) {
m_FailedPlugins.push_back(pluginName);
- qCritical("failed to load plugin %s: %s",
- qUtf8Printable(pluginName), qUtf8Printable(pluginLoader->errorString()));
+ log::error(
+ "failed to load plugin {}: {}",
+ pluginName, pluginLoader->errorString());
} else {
if (registerPlugin(pluginLoader->instance(), pluginName)) {
- qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName()));
+ log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName());
m_PluginLoaders.push_back(pluginLoader.release());
} else {
m_FailedPlugins.push_back(pluginName);
- qWarning("plugin \"%s\" failed to load (may be outdated)", qUtf8Printable(pluginName));
+ log::warn("plugin \"{}\" failed to load (may be outdated)", pluginName);
}
}
}
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 85160a88..8637f546 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -309,7 +309,7 @@ int PluginList::findPluginByPriority(int priority)
return i;
}
}
- qCritical(QString("No plugin with priority %1").arg(priority).toLocal8Bit());
+ log::error("No plugin with priority {}", priority);
return -1;
}
@@ -417,7 +417,7 @@ void PluginList::addInformation(const QString &name, const QString &message)
if (iter != m_ESPsByName.end()) {
m_AdditionalInfo[name.toLower()].m_Messages.append(message);
} else {
- qWarning("failed to associate message for \"%s\"", qUtf8Printable(name));
+ log::warn("failed to associate message for \"{}\"", name);
}
}
@@ -481,7 +481,7 @@ void PluginList::writeLockedOrder(const QString &fileName) const
file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8());
}
file.commit();
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName)));
+ log::debug("{} saved", QDir::toNativeSeparators(fileName));
}
@@ -506,7 +506,7 @@ void PluginList::saveTo(const QString &lockedOrderFileName
}
}
if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) {
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(deleterFileName)));
+ log::debug("{} saved", QDir::toNativeSeparators(deleterFileName));
}
} else if (QFile::exists(deleterFileName)) {
shellDelete(QStringList() << deleterFileName);
@@ -521,7 +521,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
return true;
}
- qDebug("setting file times on esps");
+ log::debug("setting file times on esps");
for (ESPInfo &esp : m_ESPs) {
std::wstring espName = ToWString(esp.m_Name);
@@ -694,7 +694,7 @@ void PluginList::setState(const QString &name, PluginStates state) {
m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) ||
m_ESPs[iter->second].m_ForceEnabled;
} else {
- qWarning("Plugin not found: %s", qUtf8Printable(name));
+ log::warn("Plugin not found: {}", name);
}
}
@@ -824,7 +824,7 @@ void PluginList::updateIndices()
continue;
}
if (m_ESPs[i].m_Priority >= static_cast<int>(m_ESPs.size())) {
- qCritical("invalid plugin priority: %d", m_ESPs[i].m_Priority);
+ log::error("invalid plugin priority: {}", m_ESPs[i].m_Priority);
continue;
}
m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
@@ -1067,9 +1067,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int
this->index(0, 0),
this->index(static_cast<int>(m_ESPs.size()), columnCount()));
} catch (const std::exception &e) {
- qCritical("failed to invoke state changed notification: %s", e.what());
+ log::error("failed to invoke state changed notification: {}", e.what());
} catch (...) {
- qCritical("failed to invoke state changed notification: unknown exception");
+ log::error("failed to invoke state changed notification: unknown exception");
}
}
@@ -1368,7 +1368,7 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled,
m_Masters.insert(QString(iter->c_str()));
}
} catch (const std::exception &e) {
- qCritical("failed to parse plugin file %s: %s", qUtf8Printable(fullPath), e.what());
+ log::error("failed to parse plugin file {}: {}", fullPath, e.what());
m_IsMaster = false;
m_IsLight = false;
m_IsLightFlagged = false;
diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp
index 1e8e800f..da09935b 100644
--- a/src/problemsdialog.cpp
+++ b/src/problemsdialog.cpp
@@ -96,7 +96,7 @@ void ProblemsDialog::startFix()
{
QObject *fixButton = QObject::sender();
if (fixButton == NULL) {
- qWarning("no button");
+ log::warn("no button");
return;
}
IPluginDiagnose *plugin = reinterpret_cast<IPluginDiagnose*>(fixButton ->property("fix").value<void*>());
diff --git a/src/profile.cpp b/src/profile.cpp
index 4ac15333..7f4ebcaa 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -40,7 +40,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QMessageBox>
#include <QScopedArrayPointer>
#include <QStringList> // for QStringList
-#include <QtDebug> // for qDebug, qWarning, etc
#include <QtGlobal> // for qUtf8Printable
#include <QBuffer>
#include <QDirIterator>
@@ -125,7 +124,7 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin)
findProfileSettings();
if (!QFile::exists(m_Directory.filePath("modlist.txt"))) {
- qWarning("missing modlist.txt in %s", qUtf8Printable(directory.path()));
+ log::warn("missing modlist.txt in {}", directory.path());
touchFile(m_Directory.filePath("modlist.txt"));
}
@@ -232,7 +231,6 @@ void Profile::doWriteModlist()
}
for (std::map<int, unsigned int>::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) {
- //qDebug(QString("write mod %1 to priority %2").arg(iter->first).arg(iter->second).toLocal8Bit());
// the priority order was inverted on load so it has to be inverted again
unsigned int index = iter->second;
if (index != UINT_MAX) {
@@ -253,7 +251,7 @@ void Profile::doWriteModlist()
}
if (file.commitIfDifferent(m_LastModlistHash)) {
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName)));
+ log::debug("{} saved", QDir::toNativeSeparators(fileName));
}
} catch (const std::exception &e) {
reportError(tr("failed to write mod list: %1").arg(e.what()));
@@ -267,7 +265,10 @@ void Profile::createTweakedIniFile()
QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini");
if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) {
- reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError())));
+ const auto e = GetLastError();
+ reportError(
+ tr("failed to update tweaked ini file, wrong settings may be used: %1")
+ .arg(QString::fromStdWString(formatSystemMessage(e))));
return;
}
@@ -287,9 +288,12 @@ void Profile::createTweakedIniFile()
}
if (error) {
- reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str()));
+ const auto e = ::GetLastError();
+ reportError(tr("failed to create tweaked ini: %1")
+ .arg(QString::fromStdWString(formatSystemMessage(e))));
}
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni)));
+
+ log::debug("{} saved", QDir::toNativeSeparators(tweakedIni));
}
// static
@@ -304,7 +308,7 @@ void Profile::renameModInAllProfiles(const QString& oldName, const QString& newN
if (modList.exists())
renameModInList(modList, oldName, newName);
else
- qWarning("Profile has no modlist.txt : %s", qUtf8Printable(profileIter.filePath()));
+ log::warn("Profile has no modlist.txt: {}", profileIter.filePath());
}
}
@@ -325,7 +329,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr
if (line.length() == 0) {
// ignore empty lines
- qWarning("mod list contained invalid data: empty line");
+ log::warn("mod list contained invalid data: empty line");
continue;
}
@@ -340,7 +344,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr
if (modName.isEmpty()) {
// file broken?
- qWarning("mod list contained invalid data: missing mod name");
+ log::warn("mod list contained invalid data: missing mod name");
continue;
}
@@ -361,8 +365,9 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr
}
if (renamed)
- qDebug("Renamed %d \"%s\" mod to \"%s\" in %s",
- renamed, qUtf8Printable(oldName), qUtf8Printable(newName), qUtf8Printable(modList.fileName()));
+ log::debug(
+ "Renamed {} \"{}\" mod to \"{}\" in {}",
+ renamed, oldName, newName, modList.fileName());
}
void Profile::refreshModStatus()
@@ -421,14 +426,16 @@ void Profile::refreshModStatus()
m_ModStatus[modIndex].m_Priority = index++;
}
} else {
- qWarning("no mod state for \"%s\" (profile \"%s\")",
- qUtf8Printable(modName), qUtf8Printable(m_Directory.path()));
+ log::warn(
+ "no mod state for \"{}\" (profile \"{}\")",
+ modName, m_Directory.path());
// need to rewrite the modlist to fix this
modStatusModified = true;
}
} else {
- qDebug("mod not found: \"%s\" (profile \"%s\")",
- qUtf8Printable(modName), qUtf8Printable(m_Directory.path()));
+ log::debug(
+ "mod not found: \"{}\" (profile \"{}\")",
+ modName, m_Directory.path());
// need to rewrite the modlist to fix this
modStatusModified = true;
}
@@ -492,8 +499,10 @@ 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");
+ log::warn(
+ "{}: {} - {} ({})",
+ i, info->name(), m_ModStatus[i].m_Priority,
+ m_ModStatus[i].m_Enabled ? "enabled" : "disabled");
}
}
@@ -566,7 +575,7 @@ void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QLis
QList<unsigned int> dirtyMods;
for (auto idx : modsToEnable) {
if (idx >= m_ModStatus.size()) {
- qCritical() << tr("invalid mod index: %1").arg(idx);
+ log::error("invalid mod index: {}", idx);
continue;
}
if (!m_ModStatus[idx].m_Enabled) {
@@ -576,7 +585,7 @@ void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QLis
}
for (auto idx : modsToDisable) {
if (idx >= m_ModStatus.size()) {
- qCritical() << tr("invalid mod index: %1").arg(idx);
+ log::error("invalid mod index: {}", idx);
continue;
}
if (ModInfo::getByIndex(idx)->alwaysEnabled()) {
@@ -800,7 +809,7 @@ bool Profile::localSettingsEnabled() const
QStringList missingFiles;
for (QString file : m_GamePlugin->iniFiles()) {
if (!QFile::exists(m_Directory.filePath(file))) {
- qWarning("missing %s in %s", qUtf8Printable(file), qUtf8Printable(m_Directory.path()));
+ log::warn("missing {} in {}", file, m_Directory.path());
missingFiles << file;
}
}
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp
index f9ea655f..d7863fc8 100644
--- a/src/profilesdialog.cpp
+++ b/src/profilesdialog.cpp
@@ -214,9 +214,9 @@ void ProfilesDialog::on_removeProfileButton_clicked()
delete item;
}
if (!shellDelete(QStringList(profilePath))) {
- qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(profilePath), ::GetLastError());
+ log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", profilePath, ::GetLastError());
if (!removeDir(profilePath)) {
- qWarning("regular delete failed too");
+ log::warn("regular delete failed too");
}
}
}
diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp
index 5fcb84d3..ff9539d7 100644
--- a/src/qtgroupingproxy.cpp
+++ b/src/qtgroupingproxy.cpp
@@ -18,11 +18,14 @@
#include "qtgroupingproxy.h"
+#include <log.h>
#include <QDebug>
#include <QIcon>
#include <QInputDialog>
+using namespace MOBase;
+
/*!
\class QtGroupingProxy
\brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
@@ -86,7 +89,6 @@ QtGroupingProxy::setGroupedColumn( int groupedColumn )
QList<RowData>
QtGroupingProxy::belongsTo( const QModelIndex &idx )
{
- //qDebug() << __FILE__ << __FUNCTION__;
QList<RowData> rowDataList;
//get all the data for this index from the model
@@ -106,7 +108,7 @@ QtGroupingProxy::belongsTo( const QModelIndex &idx )
i.next();
int role = i.key();
QVariant variant = i.value();
- // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant;
+
if ( variant.type() == QVariant::List )
{
//a list of variants get's expanded to multiple rows
@@ -162,7 +164,7 @@ QtGroupingProxy::buildTree()
m_parentCreateList.clear();
int max = sourceModel()->rowCount( m_rootNode );
- //qDebug() << QString("building tree with %1 leafs.").arg( max );
+
//WARNING: these have to be added in order because the addToGroups function is optimized for
//modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
for( int row = 0; row < max; row++ )
@@ -232,9 +234,6 @@ QtGroupingProxy::addSourceRow( const QModelIndex &idx )
int updatedGroup = -1;
if( !data.isEmpty() )
{
- // qDebug() << QString("index %1 belongs to group %2").arg( row )
- // .arg( data[0][Qt::DisplayRole].toString() );
-
foreach( const RowData &cachedData, m_groupMaps )
{
//when this matches the index belongs to an existing group
@@ -316,21 +315,12 @@ QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const
pc.row = parent.row();
m_parentCreateList << pc;
- //dumpParentCreateList();
- // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() );
- // for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
- // {
- // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex <<
- // " | " << m_parentCreateList[i].row;
- // }
-
return m_parentCreateList.size() - 1;
}
QModelIndex
QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
{
- // qDebug() << "index requested for: (" << row << "," << column << "), " << parent;
if( !hasIndex(row, column, parent) ) {
return QModelIndex();
}
@@ -350,17 +340,15 @@ QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
QModelIndex
QtGroupingProxy::parent( const QModelIndex &index ) const
{
- //qDebug() << "parent: " << index;
if( !index.isValid() )
return QModelIndex();
int parentCreateIndex = index.internalId();
- //qDebug() << "parentCreateIndex: " << parentCreateIndex;
if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
return QModelIndex();
struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
- //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")";
+
//only items at column 0 have children
return createIndex( pc.row, 0, pc.parentCreateIndex );
}
@@ -368,12 +356,10 @@ QtGroupingProxy::parent( const QModelIndex &index ) const
int
QtGroupingProxy::rowCount( const QModelIndex &index ) const
{
- //qDebug() << "rowCount: " << index;
if( !index.isValid() )
{
//the number of top level groups + the number of non-grouped items
int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits<quint32>::max() ).count();
- //qDebug() << rows << " in root group";
return rows;
}
@@ -382,12 +368,10 @@ QtGroupingProxy::rowCount( const QModelIndex &index ) const
{
qint64 groupIndex = index.row();
int rows = m_groupHash.value( groupIndex ).count();
- //qDebug() << rows << " in group " << m_groupMaps[groupIndex];
return rows;
} else {
QModelIndex originalIndex = mapToSource( index );
int rowCount = sourceModel()->rowCount( originalIndex );
- //qDebug() << "original item: rowCount == " << rowCount;
return rowCount;
}
}
@@ -447,7 +431,7 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const
{
if( !index.isValid() )
return QVariant();
- // qDebug() << __FUNCTION__ << index << " role: " << role;
+
int row = index.row();
int column = index.column();
if( isGroup( index ) )
@@ -495,11 +479,9 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const
}
}
- //qDebug() << __FUNCTION__ << "is a group";
//use cached or precalculated data
if( m_groupMaps[row][column].contains( Qt::DisplayRole ) )
{
- // qDebug() << "Using cached data for " << row << "x" << column << ": " << m_groupMaps[row][column].value(Qt::DisplayRole).toString();
if ((m_flags & FLAG_NOGROUPNAME) != 0) {
QModelIndex parentIndex = this->index( row, 0, index.parent() );
QModelIndex childIndex = this->index( 0, column, parentIndex );
@@ -526,18 +508,17 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const
function = mapToSource(childIndex).data(m_aggregateRole).toInt();
}
- //qDebug() << __FUNCTION__ << "childCount: " << childCount;
//Need a parentIndex with column == 0 because only those have children.
QModelIndex parentIndex = this->index( row, 0, index.parent() );
for( int childRow = 0; childRow < childCount; childRow++ )
{
QModelIndex childIndex = this->index( childRow, column, parentIndex );
QVariant data = mapToSource( childIndex ).data( role );
- //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type());
+
if( data.isValid() && !variantsOfChildren.contains( data ) )
variantsOfChildren << data;
}
- //qDebug() << "gathered this data from children: " << variantsOfChildren;
+
//saving in cache
ItemData roleMap = m_groupMaps[row].value( column );
foreach( const QVariant &variant, variantsOfChildren )
@@ -547,8 +528,6 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const
}
}
- //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role];
-
if( variantsOfChildren.count() == 0 )
return QVariant();
@@ -621,34 +600,30 @@ QtGroupingProxy::isGroup( const QModelIndex &index ) const
QModelIndex
QtGroupingProxy::mapToSource( const QModelIndex &index ) const
{
- //qDebug() << "mapToSource: " << index;
if( !index.isValid() ) {
return m_rootNode;
}
if( isGroup( index ) )
{
- //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString();
return m_rootNode;
}
QModelIndex proxyParent = index.parent();
- //qDebug() << "parent: " << proxyParent;
QModelIndex originalParent = mapToSource( proxyParent );
- //qDebug() << "originalParent: " << originalParent;
+
int originalRow = index.row();
if( originalParent == m_rootNode )
{
int indexInGroup = index.row();
if( !proxyParent.isValid() )
indexInGroup -= m_groupMaps.count();
- //qDebug() << "indexInGroup" << indexInGroup;
+
QList<int> childRows = m_groupHash.value( proxyParent.row() );
if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
return QModelIndex();
originalRow = childRows.at( indexInGroup );
- //qDebug() << "originalRow: " << originalRow;
}
return sourceModel()->index( originalRow, index.column(), originalParent );
}
@@ -674,7 +649,7 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
QModelIndex proxyParent;
QModelIndex sourceParent = idx.parent();
- //qDebug() << "sourceParent: " << sourceParent;
+
int proxyRow = idx.row();
int sourceRow = idx.row();
@@ -708,15 +683,12 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
proxyParent = QModelIndex();
// if the proxy item is not in a group it will be below the groups.
int groupLength = m_groupMaps.count();
- //qDebug() << "groupNames length: " << groupLength;
int i = m_groupHash.value( std::numeric_limits<quint32>::max() ).indexOf( sourceRow );
- //qDebug() << "index in hash: " << i;
+
proxyRow = groupLength + i;
}
}
- //qDebug() << "proxyParent: " << proxyParent;
- //qDebug() << "proxyRow: " << proxyRow;
return this->index( proxyRow, idx.column(), proxyParent );
}
@@ -731,9 +703,9 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const
return 0;
}
+
//only if the grouped column has the editable flag set allow the
//actions leading to setData on the source (edit & drop)
- // qDebug() << idx;
if( isGroup( idx ) )
{
// dumpGroups();
@@ -749,7 +721,7 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const
m_rootNode.parent() );
if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 )
{
- qDebug("row %d is not checkable", originalRow);
+ log::debug("row {} is not checkable", originalRow);
checkable = false;
}
}
@@ -892,9 +864,7 @@ QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int star
if( parent != m_rootNode )
{
//an item will be added to an original index, remap and pass it on
- // qDebug() << parent;
QModelIndex proxyParent = mapFromSource( parent );
- // qDebug() << proxyParent;
beginInsertRows( proxyParent, start, end );
}
}
@@ -914,7 +884,12 @@ QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int en
{
//an item was added to an original index, remap and pass it on
QModelIndex proxyParent = mapFromSource( parent );
- qDebug() << proxyParent;
+
+ QString s;
+ QDebug debug(&s);
+ debug << proxyParent;
+ log::debug("{}", s);
+
//beginInsertRows had to be called in modelRowsAboutToBeInserted()
endInsertRows();
}
@@ -951,9 +926,7 @@ QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start
else
{
//child item(s) of an original item will be removed, remap and pass it on
- // qDebug() << parent;
QModelIndex proxyParent = mapFromSource( parent );
- // qDebug() << proxyParent;
beginRemoveRows( proxyParent, start, end );
}
}
@@ -1044,16 +1017,24 @@ QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const
void
QtGroupingProxy::dumpGroups() const
{
- qDebug() << "m_groupHash: ";
+ QString s;
+ QDebug debug(&s);
+
+ debug << "m_groupHash:\n";
for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ )
{
- qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex );
+ debug << groupIndex << " : " << m_groupHash.value( groupIndex ) << "\n";
}
- qDebug() << "m_groupMaps: ";
+ debug << "m_groupMaps:\n";
for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
- qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex );
- qDebug() << m_groupHash.value( std::numeric_limits<quint32>::max() );
+ {
+ debug << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ) << "\n";
+ }
+
+ debug << m_groupHash.value( std::numeric_limits<quint32>::max() );
+
+ log::debug("{}", s);
}
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp
index e967b27c..0ca39b19 100644
--- a/src/selfupdater.cpp
+++ b/src/selfupdater.cpp
@@ -150,23 +150,23 @@ void SelfUpdater::testForUpdate()
VersionInfo newestVer(newest["tag_name"].toString());
if (newestVer > this->m_MOVersion) {
m_UpdateCandidate = newest;
- qDebug("update available: %s -> %s",
- qUtf8Printable(this->m_MOVersion.displayString(3)),
- qUtf8Printable(newestVer.displayString(3)));
+ log::debug("update available: {} -> {}",
+ this->m_MOVersion.displayString(3),
+ newestVer.displayString(3));
emit updateAvailable();
} else if (newestVer < this->m_MOVersion) {
// this could happen if the user switches from using prereleases to
// stable builds. Should we downgrade?
- qDebug("This version is newer than the latest released one: %s -> %s",
- qUtf8Printable(this->m_MOVersion.displayString(3)),
- qUtf8Printable(newestVer.displayString(3)));
+ log::debug("This version is newer than the latest released one: {} -> {}",
+ this->m_MOVersion.displayString(3),
+ newestVer.displayString(3));
}
}
});
}
//Catch all is bad by design, should be improved
catch (...) {
- qDebug("Unable to connect to github.com to check version");
+ log::debug("Unable to connect to github.com to check version");
}
}
@@ -230,7 +230,7 @@ void SelfUpdater::closeProgress()
void SelfUpdater::openOutputFile(const QString &fileName)
{
QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName;
- qDebug("downloading to %s", qUtf8Printable(outputPath));
+ log::debug("downloading to {}", outputPath);
m_UpdateFile.setFileName(outputPath);
m_UpdateFile.open(QIODevice::WriteOnly);
}
@@ -312,7 +312,7 @@ void SelfUpdater::downloadFinished()
return;
}
- qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData());
+ log::debug("download: {}", m_UpdateFile.fileName());
try {
installUpdate();
diff --git a/src/settings.cpp b/src/settings.cpp
index 5cb2524f..5ad066b2 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -54,7 +54,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QPalette>
#include <Qt> // for Qt::UserRole, etc
-#include <QtDebug> // for qDebug, qWarning
#include <Windows.h> // For ShellExecuteW, HINSTANCE, etc
#include <wincred.h> // For storage
@@ -165,8 +164,9 @@ void Settings::registerPlugin(IPlugin *plugin)
for (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",
- qUtf8Printable(temp.toString()), qUtf8Printable(setting.key), qUtf8Printable(plugin->name()));
+ log::warn(
+ "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default",
+ temp.toString(), setting.key, plugin->name());
temp = setting.defaultValue;
}
m_PluginSettings[plugin->name()][setting.key] = temp;
@@ -220,9 +220,7 @@ QString Settings::deObfuscate(const QString key)
} else {
const auto e = GetLastError();
if (e != ERROR_NOT_FOUND) {
- qCritical().nospace()
- << "Retrieving encrypted data failed: "
- << formatSystemMessageQ(e);
+ log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e));
}
}
delete[] keyData;
@@ -367,11 +365,7 @@ bool Settings::setNexusApiKey(const QString& apiKey)
{
if (!obfuscate("APIKEY", apiKey)) {
const auto e = GetLastError();
-
- qCritical().nospace()
- << "Storing API key failed: "
- << formatSystemMessageQ(e);
-
+ log::error("Storing API key failed: {}", formatSystemMessage(e));
return false;
}
@@ -415,9 +409,14 @@ bool Settings::offlineMode() const
return m_Settings.value("Settings/offline_mode", false).toBool();
}
-int Settings::logLevel() const
+log::Levels Settings::logLevel() const
+{
+ return static_cast<log::Levels>(m_Settings.value("Settings/log_level").toInt());
+}
+
+void Settings::setLogLevel(log::Levels level)
{
- return m_Settings.value("Settings/log_level", static_cast<int>(LogLevel::Info)).toInt();
+ m_Settings.setValue("Settings/log_level", static_cast<int>(level));
}
int Settings::crashDumpsType() const
@@ -487,9 +486,7 @@ void Settings::setSteamLogin(QString username, QString password)
}
if (!obfuscate("steam_password", password)) {
const auto e = GetLastError();
- qCritical().nospace()
- << "Storing or deleting password failed: "
- << formatSystemMessageQ(e);
+ log::error("Storing or deleting password failed: {}", formatSystemMessage(e));
}
}
@@ -637,7 +634,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers)
QVariantMap val = m_Settings.value(key).toMap();
QDate lastSeen = val["lastSeen"].toDate();
if (lastSeen.daysTo(now) > 30) {
- qDebug("removing server %s since it hasn't been available for downloads in over a month", qUtf8Printable(key));
+ log::debug("removing server {} since it hasn't been available for downloads in over a month", key);
m_Settings.remove(key);
}
}
@@ -760,10 +757,10 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent)
if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password"))
{
if (first_update) {
- qDebug("Changed settings:");
+ log::debug("Changed settings:");
first_update = false;
}
- qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data());
+ log::debug(" {}={}", k, m_Settings.value(k).toString());
}
m_Settings.endGroup();
}
@@ -1000,7 +997,7 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d
, m_dumpsMaxEdit(m_dialog.findChild<QSpinBox *>("dumpsMaxEdit"))
, m_diagnosticsExplainedLabel(m_dialog.findChild<QLabel *>("diagnosticsExplainedLabel"))
{
- m_logLevelBox->setCurrentIndex(m_parent->logLevel());
+ setLevelsBox();
m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType());
m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax());
QString logsPath = qApp->property("dataPath").toString()
@@ -1016,11 +1013,28 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d
void Settings::DiagnosticsTab::update()
{
- m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex());
+ m_Settings.setValue("Settings/log_level", m_logLevelBox->currentData().toInt());
m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex());
m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value());
}
+void Settings::DiagnosticsTab::setLevelsBox()
+{
+ m_logLevelBox->clear();
+
+ m_logLevelBox->addItem(tr("Debug"), log::Debug);
+ m_logLevelBox->addItem(tr("Info (recommended)"), log::Info);
+ m_logLevelBox->addItem(tr("Warning"), log::Warning);
+ m_logLevelBox->addItem(tr("Error"), log::Error);
+
+ for (int i=0; i<m_logLevelBox->count(); ++i) {
+ if (m_logLevelBox->itemData(i) == m_parent->logLevel()) {
+ m_logLevelBox->setCurrentIndex(i);
+ break;
+ }
+ }
+}
+
Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog)
: Settings::SettingsTab(parent, dialog)
, m_offlineBox(dialog.findChild<QCheckBox *>("offlineBox"))
diff --git a/src/settings.h b/src/settings.h
index bccd1e81..c66eb94c 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#define SETTINGS_H
#include "loadmechanism.h"
+#include <log.h>
#include <QList>
#include <QMap>
@@ -232,7 +233,12 @@ public:
/**
* @return the configured log level
*/
- int logLevel() const;
+ MOBase::log::Levels logLevel() const;
+
+ /**
+ * sets the log level setting
+ */
+ void setLogLevel(MOBase::log::Levels level);
/**
* @return the configured crash dumps type
@@ -481,6 +487,8 @@ private:
QComboBox *m_dumpsTypeBox;
QSpinBox *m_dumpsMaxEdit;
QLabel *m_diagnosticsExplainedLabel;
+
+ void setLevelsBox();
};
/** Display/store the configuration in the 'nexus' tab of the settings dialogue */
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index fccc8be0..1e94bcde 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -1426,26 +1426,6 @@ programs you are intentionally running.</string>
&quot;Debug&quot; produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the &quot;Info&quot; level for regular use. On the &quot;Error&quot; level the log file usually remains empty.
</string>
</property>
- <item>
- <property name="text">
- <string>Debug</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Info (recommended)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Warning</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Error</string>
- </property>
- </item>
</widget>
</item>
</layout>
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp
index bde515a9..2cdbac74 100644
--- a/src/shared/directoryentry.cpp
+++ b/src/shared/directoryentry.cpp
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "windows_error.h"
#include "leaktrace.h"
#include "error_report.h"
+#include <log.h>
#include <bsatk.h>
#include <boost/bind.hpp>
#include <boost/scoped_array.hpp>
@@ -35,6 +36,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
namespace MOShared {
+namespace log = MOBase::log;
+
static const int MAXPATH_UNICODE = 32767;
class OriginConnection {
@@ -103,7 +106,7 @@ public:
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());
+ log::error("failed to change name lookup from {} to {}", oldName, newName);
}
}
@@ -714,14 +717,14 @@ void DirectoryEntry::removeFile(FileEntry::Index 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());
+ log::error(
+ "file \"{}\" not in directory \"{}\"",
+ m_FileRegister->getFile(index)->getName(), this->getName());
}
} else {
- log("file \"%ls\" not in directory \"%ls\", directory empty",
- m_FileRegister->getFile(index)->getName().c_str(),
- this->getName().c_str());
+ log::error(
+ "file \"{}\" not in directory \"{}\", directory empty",
+ m_FileRegister->getFile(index)->getName(), this->getName());
}
}
@@ -844,7 +847,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const
DirectoryEntry *temp = findSubDirectory(pathComponent);
if (temp != nullptr) {
if (len >= path.size()) {
- log("unexpected end of path");
+ log::error("unexpected end of path");
return FileEntry::Ptr();
}
return temp->searchFile(path.substr(len + 1), directory);
@@ -988,7 +991,7 @@ bool FileRegister::removeFile(FileEntry::Index index)
m_Files.erase(index);
return true;
} else {
- log("invalid file index for remove: %lu", index);
+ log::error("invalid file index for remove: {}", index);
return false;
}
}
@@ -1002,7 +1005,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID)
m_Files.erase(iter);
}
} else {
- log("invalid file index for remove (for origin): %lu", index);
+ log::error("invalid file index for remove (for origin): {}", index);
}
}
diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp
index 6d091630..4185b544 100644
--- a/src/shared/error_report.cpp
+++ b/src/shared/error_report.cpp
@@ -23,7 +23,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
namespace MOShared {
-
void reportError(LPCSTR format, ...)
{
char buffer[1025];
@@ -52,48 +51,4 @@ void reportError(LPCWSTR format, ...)
MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR);
}
-
-std::string getCurrentErrorStringA()
-{
- LPSTR buffer = nullptr;
-
- DWORD errorCode = ::GetLastError();
-
- if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) {
- ::SetLastError(errorCode);
- return std::string();
- } else {
- LPSTR lastChar = buffer + strlen(buffer) - 2;
- *lastChar = '\0';
-
- std::string result(buffer);
-
- LocalFree(buffer);
- ::SetLastError(errorCode);
- return result;
- }
-}
-
-std::wstring getCurrentErrorStringW()
-{
- LPWSTR buffer = nullptr;
-
- DWORD errorCode = ::GetLastError();
-
- if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) {
- ::SetLastError(errorCode);
- return std::wstring();
- } else {
- LPWSTR lastChar = buffer + wcslen(buffer) - 2;
- *lastChar = '\0';
-
- std::wstring result(buffer);
-
- LocalFree(buffer);
- ::SetLastError(errorCode);
- return result;
- }
-}
} // namespace MOShared
diff --git a/src/shared/error_report.h b/src/shared/error_report.h
index c09ad75b..17b25645 100644
--- a/src/shared/error_report.h
+++ b/src/shared/error_report.h
@@ -24,28 +24,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <Windows.h>
#include <string>
-namespace std {
-#ifdef UNICODE
-typedef wstring tstring;
-#else
-typedef string tstring;
-#endif
-}
-
-extern void log(const char* format, ...);
-
namespace MOShared {
void reportError(LPCSTR format, ...);
void reportError(LPCWSTR format, ...);
-std::string getCurrentErrorStringA();
-std::wstring getCurrentErrorStringW();
-
-#ifdef UNICODE
-#define getCurrentErrorString getCurrentErrorStringW
-#else
-#define getCurrentErrorString getCurrentErrorStringA
-#endif
-
} // namespace MOShared
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index 17df3b92..07983e12 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -19,34 +19,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "util.h"
#include "windows_error.h"
-#include "error_report.h"
-#include "executableslist.h"
-#include "instancemanager.h"
-#include <utility.h>
-
-#include <sstream>
-#include <locale>
-#include <algorithm>
-#include <set>
-#include <filesystem>
-
-#include <DbgHelp.h>
-#include <boost/scoped_array.hpp>
-#include <QApplication>
-
-#include <comdef.h>
-#include <Wbemidl.h>
-#include <wscapi.h>
-#include <netfw.h>
-
-#pragma comment(lib, "Wbemuuid.lib")
-
-using MOBase::formatSystemMessage;
-using MOBase::formatSystemMessageQ;
-namespace fs = std::filesystem;
-
-namespace MOShared {
+namespace MOShared
+{
bool FileExists(const std::string &filename)
{
@@ -268,1808 +243,4 @@ MOBase::VersionInfo createVersionInfo()
}
}
-
-namespace env
-{
-
-struct HandleCloser
-{
- using pointer = HANDLE;
-
- void operator()(HANDLE h)
- {
- if (h != INVALID_HANDLE_VALUE) {
- ::CloseHandle(h);
- }
- }
-};
-
-using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>;
-
-
-struct LibraryFreer
-{
- using pointer = HINSTANCE;
-
- void operator()(HINSTANCE h)
- {
- if (h != 0) {
- ::FreeLibrary(h);
- }
- }
-};
-
-struct COMReleaser
-{
- void operator()(IUnknown* p)
- {
- if (p) {
- p->Release();
- }
- }
-};
-
-
-template <class T>
-using COMPtr = std::unique_ptr<T, COMReleaser>;
-
-
-class ShellLinkException
-{
-public:
- ShellLinkException(QString s)
- : m_what(std::move(s))
- {
- }
-
- const QString& what() const
- {
- return m_what;
- }
-
-private:
- QString m_what;
-};
-
-// just a wrapper around IShellLink operations that throws ShellLinkException
-// on errors
-//
-class ShellLinkWrapper
-{
-public:
- ShellLinkWrapper()
- {
- m_link = createShellLink();
- m_file = createPersistFile();
- }
-
- void setPath(const QString& s)
- {
- if (s.isEmpty()) {
- throw ShellLinkException("path cannot be empty");
- }
-
- const auto r = m_link->SetPath(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set target path '%1'").arg(s));
- }
-
- void setArguments(const QString& s)
- {
- const auto r = m_link->SetArguments(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set arguments '%1'").arg(s));
- }
-
- void setDescription(const QString& s)
- {
- if (s.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetDescription(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set description '%1'").arg(s));
- }
-
- void setIcon(const QString& file, int i)
- {
- if (file.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i);
- throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i));
- }
-
- void setWorkingDirectory(const QString& s)
- {
- if (s.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set working directory '%1'").arg(s));
- }
-
- void save(const QString& path)
- {
- const auto r = m_file->Save(path.toStdWString().c_str(), TRUE);
- throwOnFail(r, QString("failed to save link '%1'").arg(path));
- }
-
-private:
- COMPtr<IShellLink> m_link;
- COMPtr<IPersistFile> m_file;
-
- void throwOnFail(HRESULT r, const QString& s)
- {
- if (FAILED(r)) {
- throw ShellLinkException(QString("%1, %2")
- .arg(s)
- .arg(formatSystemMessageQ(r)));
- }
- }
-
- COMPtr<IShellLink> createShellLink()
- {
- void* link = nullptr;
-
- const auto r = CoCreateInstance(
- CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
- IID_IShellLink, &link);
-
- throwOnFail(r, "failed to create IShellLink instance");
-
- if (!link) {
- throw ShellLinkException("creating IShellLink worked, pointer is null");
- }
-
- return COMPtr<IShellLink>(static_cast<IShellLink*>(link));
- }
-
- COMPtr<IPersistFile> createPersistFile()
- {
- void* file = nullptr;
-
- const auto r = m_link->QueryInterface(IID_IPersistFile, &file);
- throwOnFail(r, "failed to get IPersistFile interface");
-
- if (!file) {
- throw ShellLinkException("querying IPersistFile worked, pointer is null");
- }
-
- return COMPtr<IPersistFile>(static_cast<IPersistFile*>(file));
- }
-};
-
-
-Shortcut::Shortcut()
- : m_iconIndex(0)
-{
-}
-
-Shortcut::Shortcut(const Executable& exe)
- : Shortcut()
-{
- m_name = exe.title();
- m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
-
- m_arguments = QString("\"moshortcut://%1:%2\"")
- .arg(InstanceManager::instance().currentInstance())
- .arg(exe.title());
-
- m_description = QString("Run %1 with ModOrganizer").arg(exe.title());
-
- if (exe.usesOwnIcon()) {
- m_icon = exe.binaryInfo().absoluteFilePath();
- }
-
- m_workingDirectory = qApp->applicationDirPath();
-}
-
-Shortcut& Shortcut::name(const QString& s)
-{
- m_name = s;
- return *this;
-}
-
-Shortcut& Shortcut::target(const QString& s)
-{
- m_target = s;
- return *this;
-}
-
-Shortcut& Shortcut::arguments(const QString& s)
-{
- m_arguments = s;
- return *this;
-}
-
-Shortcut& Shortcut::description(const QString& s)
-{
- m_description = s;
- return *this;
-}
-
-Shortcut& Shortcut::icon(const QString& s, int index)
-{
- m_icon = s;
- m_iconIndex = index;
- return *this;
-}
-
-Shortcut& Shortcut::workingDirectory(const QString& s)
-{
- m_workingDirectory = s;
- return *this;
-}
-
-bool Shortcut::exists(Locations loc) const
-{
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- return QFileInfo(path).exists();
-}
-
-bool Shortcut::toggle(Locations loc)
-{
- if (exists(loc)) {
- return remove(loc);
- } else {
- return add(loc);
- }
-}
-
-bool Shortcut::add(Locations loc)
-{
- debug()
- << "adding shortcut to " << toString(loc) << ":\n"
- << " . name: '" << m_name << "'\n"
- << " . target: '" << m_target << "'\n"
- << " . arguments: '" << m_arguments << "'\n"
- << " . description: '" << m_description << "'\n"
- << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n"
- << " . working directory: '" << m_workingDirectory << "'";
-
- if (m_target.isEmpty()) {
- critical() << "target is empty";
- return false;
- }
-
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- debug() << "shorcut file will be saved at '" << path << "'";
-
- try
- {
- ShellLinkWrapper link;
-
- link.setPath(m_target);
- link.setArguments(m_arguments);
- link.setDescription(m_description);
- link.setIcon(m_icon, m_iconIndex);
- link.setWorkingDirectory(m_workingDirectory);
-
- link.save(path);
-
- return true;
- }
- catch(ShellLinkException& e)
- {
- critical() << e.what() << "\nshortcut file was not saved";
- }
-
- return false;
-}
-
-bool Shortcut::remove(Locations loc)
-{
- debug() << "removing shortcut for '" << m_name << "' from " << toString(loc);
-
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- debug() << "path to shortcut file is '" << path << "'";
-
- if (!QFile::exists(path)) {
- critical() << "can't remove '" << path << "', file not found";
- return false;
- }
-
- if (!MOBase::shellDelete({path})) {
- const auto e = ::GetLastError();
-
- critical()
- << "failed to remove '" << path << "', "
- << formatSystemMessageQ(e);
-
- return false;
- }
-
- return true;
-}
-
-QString Shortcut::shortcutPath(Locations loc) const
-{
- const auto dir = shortcutDirectory(loc);
- if (dir.isEmpty()) {
- return {};
- }
-
- const auto file = shortcutFilename();
- if (file.isEmpty()) {
- return {};
- }
-
- return dir + QDir::separator() + file;
-}
-
-QString Shortcut::shortcutDirectory(Locations loc) const
-{
- QString dir;
-
- try
- {
- switch (loc)
- {
- case Desktop:
- dir = MOBase::getDesktopDirectory();
- break;
-
- case StartMenu:
- dir = MOBase::getStartMenuDirectory();
- break;
-
- case None:
- default:
- critical() << "bad location " << loc;
- break;
- }
- }
- catch(std::exception&)
- {
- }
-
- return QDir::toNativeSeparators(dir);
-}
-
-QString Shortcut::shortcutFilename() const
-{
- if (m_name.isEmpty()) {
- critical() << "name is empty";
- return {};
- }
-
- return m_name + ".lnk";
-}
-
-QDebug Shortcut::debug() const
-{
- return qDebug().noquote().nospace() << "system shortcut: ";
-}
-
-QDebug Shortcut::critical() const
-{
- return qCritical().noquote().nospace() << "system shortcut: ";
-}
-
-
-QString toString(Shortcut::Locations loc)
-{
- switch (loc)
- {
- case Shortcut::None:
- return "none";
-
- case Shortcut::Desktop:
- return "desktop";
-
- case Shortcut::StartMenu:
- return "start menu";
-
- default:
- return QString("? (%1)").arg(static_cast<int>(loc));
- }
-}
-
-
-
-class WMI
-{
-public:
- class failed {};
-
- WMI(const std::string& ns)
- {
- try
- {
- createLocator();
- createService(ns);
- setSecurity();
- }
- catch(failed&)
- {
- }
- }
-
- template <class F>
- void query(const std::string& q, F&& f)
- {
- if (!m_locator || !m_service) {
- return;
- }
-
- auto enumerator = getEnumerator(q);
- if (!enumerator) {
- return;
- }
-
- for (;;)
- {
- COMPtr<IWbemClassObject> object;
-
- {
- IWbemClassObject* rawObject = nullptr;
- ULONG count = 0;
- auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count);
-
- if (count == 0 || !rawObject) {
- break;
- }
-
- if (FAILED(ret)) {
- qCritical()
- << "enumerator->next() failed, " << formatSystemMessageQ(ret);
- break;
- }
-
- object.reset(rawObject);
- }
-
- f(object.get());
- }
- }
-
-private:
- COMPtr<IWbemLocator> m_locator;
- COMPtr<IWbemServices> m_service;
-
- void createLocator()
- {
- void* rawLocator = nullptr;
-
- const auto ret = CoCreateInstance(
- CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
- IID_IWbemLocator, &rawLocator);
-
- if (FAILED(ret) || !rawLocator) {
- qCritical()
- << "CoCreateInstance for WbemLocator failed, "
- << formatSystemMessageQ(ret);
-
- throw failed();
- }
-
- m_locator.reset(static_cast<IWbemLocator*>(rawLocator));
- }
-
- void createService(const std::string& ns)
- {
- IWbemServices* rawService = nullptr;
-
- const auto res = m_locator->ConnectServer(
- _bstr_t(ns.c_str()),
- nullptr, nullptr, nullptr, 0, nullptr, nullptr,
- &rawService);
-
- if (FAILED(res) || !rawService) {
- qCritical()
- << "locator->ConnectServer() failed for namespace "
- << "'" << QString::fromStdString(ns) << "', "
- << formatSystemMessageQ(res);
-
- throw failed();
- }
-
- m_service.reset(rawService);
- }
-
- void setSecurity()
- {
- auto ret = CoSetProxyBlanket(
- m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
- RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE);
-
- if (FAILED(ret))
- {
- qCritical()
- << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret);
-
- throw failed();
- }
- }
-
- COMPtr<IEnumWbemClassObject> getEnumerator(
- const std::string& query)
- {
- IEnumWbemClassObject* rawEnumerator = NULL;
-
- auto ret = m_service->ExecQuery(
- bstr_t("WQL"),
- bstr_t(query.c_str()),
- WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
- NULL,
- &rawEnumerator);
-
- if (FAILED(ret) || !rawEnumerator)
- {
- qCritical()
- << "query '" << QString::fromStdString(query) << "' failed, "
- << formatSystemMessageQ(ret);
-
- return {};
- }
-
- return COMPtr<IEnumWbemClassObject>(rawEnumerator);
- }
-};
-
-
-Environment::Environment()
-{
- m_modules = getLoadedModules();
- m_security = getSecurityProducts();
-}
-
-const std::vector<Module>& Environment::loadedModules()
-{
- return m_modules;
-}
-
-const WindowsInfo& Environment::windowsInfo() const
-{
- return m_windows;
-}
-
-const std::vector<SecurityProduct>& Environment::securityProducts() const
-{
- return m_security;
-}
-
-std::vector<Module> Environment::getLoadedModules() const
-{
- HandlePtr snapshot(CreateToolhelp32Snapshot(
- TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId()));
-
- if (snapshot.get() == INVALID_HANDLE_VALUE)
- {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "CreateToolhelp32Snapshot() failed, "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- MODULEENTRY32 me = {};
- me.dwSize = sizeof(me);
-
- // first module, this shouldn't fail because there's at least the executable
- if (!Module32First(snapshot.get(), &me))
- {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "Module32First() failed, " << formatSystemMessageQ(e);
-
- return {};
- }
-
- std::vector<Module> v;
-
- for (;;)
- {
- const auto path = QString::fromWCharArray(me.szExePath);
- if (!path.isEmpty()) {
- v.push_back(Module(path, me.modBaseSize));
- }
-
- // next module
- if (!Module32Next(snapshot.get(), &me)) {
- const auto e = GetLastError();
-
- // no more modules is not an error
- if (e != ERROR_NO_MORE_FILES) {
- qCritical().nospace().noquote()
- << "Module32Next() failed, " << formatSystemMessageQ(e);
- }
-
- break;
- }
- }
-
- // sorting by display name
- std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) {
- return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0);
- });
-
- return v;
-}
-
-std::vector<SecurityProduct> Environment::getSecurityProducts() const
-{
- std::vector<SecurityProduct> v;
-
- {
- auto fromWMI = getSecurityProductsFromWMI();
- v.insert(
- v.end(),
- std::make_move_iterator(fromWMI.begin()),
- std::make_move_iterator(fromWMI.end()));
- }
-
- if (auto p=getWindowsFirewall()) {
- v.push_back(std::move(*p));
- }
-
- return v;
-}
-
-std::vector<SecurityProduct> Environment::getSecurityProductsFromWMI() const
-{
- // some products may be present in multiple queries, such as a product marked
- // as both antivirus and antispyware, but they'll have the same GUID, so use
- // that to avoid duplicating entries
- std::map<QUuid, SecurityProduct> map;
-
- auto handleProduct = [&](auto* o) {
- VARIANT prop;
-
- // display name
- auto ret = o->Get(L"displayName", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get displayName, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_BSTR) {
- qCritical() << "displayName is a " << prop.vt << ", not a bstr";
- return;
- }
-
- const std::wstring name = prop.bstrVal;
- VariantClear(&prop);
-
- // product state
- ret = o->Get(L"productState", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get productState, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_UI4 && prop.vt != VT_I4) {
- qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4";
- return;
- }
-
- DWORD state = 0;
- if (prop.vt == VT_I4) {
- state = prop.lVal;
- } else {
- state = prop.ulVal;
- }
-
- VariantClear(&prop);
-
- // guid
- ret = o->Get(L"instanceGuid", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get instanceGuid, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_BSTR) {
- qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr";
- return;
- }
-
- const QUuid guid(QString::fromWCharArray(prop.bstrVal));
- VariantClear(&prop);
-
- const auto provider = static_cast<int>((state >> 16) & 0xff);
- const auto scanner = (state >> 8) & 0xff;
- const auto definitions = state & 0xff;
-
- const bool active = ((scanner & 0x10) != 0);
- const bool upToDate = (definitions == 0);
-
- map.insert({
- guid,
- {QString::fromStdWString(name), provider, active, upToDate}});
- };
-
- {
- WMI wmi("root\\SecurityCenter2");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
- }
-
- {
- WMI wmi("root\\SecurityCenter");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
- }
-
- std::vector<SecurityProduct> v;
-
- for (auto&& p : map) {
- v.push_back(p.second);
- }
-
- return v;
-}
-
-std::optional<SecurityProduct> Environment::getWindowsFirewall() const
-{
- HRESULT hr = 0;
-
- COMPtr<INetFwPolicy2> policy;
-
- {
- void* rawPolicy = nullptr;
-
- hr = CoCreateInstance(
- __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER,
- __uuidof(INetFwPolicy2), &rawPolicy);
-
- if (FAILED(hr) || !rawPolicy) {
- qCritical()
- << "CoCreateInstance for NetFwPolicy2 failed, "
- << formatSystemMessageQ(hr);
-
- return {};
- }
-
- policy.reset(static_cast<INetFwPolicy2*>(rawPolicy));
- }
-
- VARIANT_BOOL enabledVariant;
-
- if (policy) {
- hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant);
- if (FAILED(hr))
- {
- qCritical()
- << "get_FirewallEnabled failed, "
- << formatSystemMessageQ(hr);
-
- return {};
- }
- }
-
- const auto enabled = (enabledVariant != VARIANT_FALSE);
- if (!enabled) {
- return {};
- }
-
- return SecurityProduct(
- "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true);
-}
-
-
-Module::Module(QString path, std::size_t fileSize)
- : m_path(std::move(path)), m_fileSize(fileSize)
-{
- const auto fi = getFileInfo();
-
- m_version = getVersion(fi.ffi);
- m_timestamp = getTimestamp(fi.ffi);
- m_versionString = fi.fileDescription;
- m_md5 = getMD5();
-}
-
-const QString& Module::path() const
-{
- return m_path;
-}
-
-QString Module::displayPath() const
-{
- return QDir::fromNativeSeparators(m_path.toLower());
-}
-
-std::size_t Module::fileSize() const
-{
- return m_fileSize;
-}
-
-const QString& Module::version() const
-{
- return m_version;
-}
-
-const QString& Module::versionString() const
-{
- return m_versionString;
-}
-
-const QDateTime& Module::timestamp() const
-{
- return m_timestamp;
-}
-
-const QString& Module::md5() const
-{
- return m_md5;
-}
-
-QString Module::timestampString() const
-{
- if (!m_timestamp.isValid()) {
- return "(no timestamp)";
- }
-
- return m_timestamp.toString(Qt::DateFormat::ISODate);
-}
-
-QString Module::toString() const
-{
- QStringList sl;
-
- // file size
- sl.push_back(displayPath());
- sl.push_back(QString("%1 B").arg(m_fileSize));
-
- // version
- if (m_version.isEmpty() && m_versionString.isEmpty()) {
- sl.push_back("(no version)");
- } else {
- if (!m_version.isEmpty()) {
- sl.push_back(m_version);
- }
-
- if (!m_versionString.isEmpty() && m_versionString != m_version) {
- sl.push_back(versionString());
- }
- }
-
- // timestamp
- if (m_timestamp.isValid()) {
- sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate));
- } else {
- sl.push_back("(no timestamp)");
- }
-
- // md5
- if (!m_md5.isEmpty()) {
- sl.push_back(m_md5);
- }
-
- return sl.join(", ");
-}
-
-Module::FileInfo Module::getFileInfo() const
-{
- const auto wspath = m_path.toStdWString();
-
- // getting version info size
- DWORD dummy = 0;
- const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy);
-
- if (size == 0) {
- const auto e = GetLastError();
-
- if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) {
- // not an error, no version information built into that module
- return {};
- }
-
- qCritical().nospace().noquote()
- << "GetFileVersionInfoSizeW() failed on '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // getting version info
- auto buffer = std::make_unique<std::byte[]>(size);
-
- if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "GetFileVersionInfoW() failed on '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // the version info has two major parts: a fixed version and a localizable
- // set of strings
-
- FileInfo fi;
- fi.ffi = getFixedFileInfo(buffer.get());
- fi.fileDescription = getFileDescription(buffer.get());
-
- return fi;
-}
-
-VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const
-{
- void* valuePointer = nullptr;
- unsigned int valueSize = 0;
-
- // the fixed version info is in the root
- const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- // not an error, no fixed file info
- return {};
- }
-
- const auto* fi = static_cast<VS_FIXEDFILEINFO*>(valuePointer);
-
- // signature is always 0xfeef04bd
- if (fi->dwSignature != 0xfeef04bd) {
- qCritical().nospace().noquote()
- << "bad file info signature 0x" << hex << fi->dwSignature << " for "
- << "'" << m_path << "'";
-
- return {};
- }
-
- return *fi;
-}
-
-QString Module::getFileDescription(std::byte* buffer) const
-{
- struct LANGANDCODEPAGE
- {
- WORD wLanguage;
- WORD wCodePage;
- };
-
- void* valuePointer = nullptr;
- unsigned int valueSize = 0;
-
- // getting list of available languages
- auto ret = VerQueryValueW(
- buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- qCritical().nospace().noquote()
- << "VerQueryValueW() for translations failed on '" << m_path << "'";
-
- return {};
- }
-
- // number of languages
- const auto count = valueSize / sizeof(LANGANDCODEPAGE);
- if (count == 0) {
- return {};
- }
-
- // using the first language in the list to get FileVersion
- const auto* lcp = static_cast<LANGANDCODEPAGE*>(valuePointer);
-
- const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion")
- .arg(lcp->wLanguage, 4, 16, QChar('0'))
- .arg(lcp->wCodePage, 4, 16, QChar('0'));
-
- ret = VerQueryValueW(
- buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- // not an error, no file version
- return {};
- }
-
- // valueSize includes the null terminator
- return QString::fromWCharArray(
- static_cast<wchar_t*>(valuePointer), valueSize - 1);
-}
-
-QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const
-{
- if (fi.dwSignature == 0) {
- return {};
- }
-
- const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff;
- const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff;
- const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff;
- const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff;
-
- if (major == 0 && minor == 0 && maintenance == 0 && build == 0) {
- return {};
- }
-
- return QString("%1.%2.%3.%4")
- .arg(major).arg(minor).arg(maintenance).arg(build);
-}
-
-QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const
-{
- FILETIME ft = {};
-
- if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) {
- // if the file info is invalid or doesn't have a date, use the creation
- // time on the file
-
- // opening the file
- HandlePtr h(CreateFileW(
- m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
-
- if (h.get() == INVALID_HANDLE_VALUE) {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "can't open file '" << m_path << "' for timestamp, "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // getting the file time
- if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) {
- const auto e = GetLastError();
- qCritical().nospace().noquote()
- << "can't get file time for '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
- } else {
- // use the time from the file info
- ft.dwHighDateTime = fi.dwFileDateMS;
- ft.dwLowDateTime = fi.dwFileDateLS;
- }
-
-
- // converting to SYSTEMTIME
- SYSTEMTIME utc = {};
-
- if (!FileTimeToSystemTime(&ft, &utc)) {
- qCritical().nospace().noquote()
- << "FileTimeToSystemTime() failed on timestamp "
- << "high=0x" << hex << ft.dwHighDateTime << " "
- << "low=0x" << hex << ft.dwLowDateTime << " for "
- << "'" << m_path << "'";
-
- return {};
- }
-
- return QDateTime(
- QDate(utc.wYear, utc.wMonth, utc.wDay),
- QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds));
-}
-
-QString Module::getMD5() const
-{
- if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) {
- // don't calculate md5 for system files, it's not really relevant and
- // it takes a while
- return {};
- }
-
- // opening the file
- QFile f(m_path);
-
- if (!f.open(QFile::ReadOnly)) {
- qCritical().nospace().noquote()
- << "failed to open file '" << m_path << "' for md5";
-
- return {};
- }
-
- // hashing
- QCryptographicHash hash(QCryptographicHash::Md5);
- if (!hash.addData(&f)) {
- qCritical().nospace().noquote()
- << "failed to calculate md5 for '" << m_path << "'";
-
- return {};
- }
-
- return hash.result().toHex();
-}
-
-
-WindowsInfo::WindowsInfo()
-{
- // loading ntdll.dll, the functions will be found with GetProcAddress()
- std::unique_ptr<HINSTANCE, LibraryFreer> ntdll(LoadLibraryW(L"ntdll.dll"));
-
- if (!ntdll) {
- qCritical() << "failed to load ntdll.dll while getting version";
- return;
- } else {
- m_reported = getReportedVersion(ntdll.get());
- m_real = getRealVersion(ntdll.get());
- }
-
- m_release = getRelease();
- m_elevated = getElevated();
-}
-
-bool WindowsInfo::compatibilityMode() const
-{
- if (m_real == Version()) {
- // don't know the real version, can't guess compatibility mode
- return false;
- }
-
- return (m_real != m_reported);
-}
-
-const WindowsInfo::Version& WindowsInfo::reportedVersion() const
-{
- return m_reported;
-}
-
-const WindowsInfo::Version& WindowsInfo::realVersion() const
-{
- return m_real;
-}
-
-const WindowsInfo::Release& WindowsInfo::release() const
-{
- return m_release;
-}
-
-std::optional<bool> WindowsInfo::isElevated() const
-{
- return m_elevated;
-}
-
-QString WindowsInfo::toString() const
-{
- QStringList sl;
-
- const QString reported = m_reported.toString();
- const QString real = m_real.toString();
-
- // version
- sl.push_back("version: " + reported);
-
- // real version if different
- if (compatibilityMode()) {
- sl.push_back("real version: " + real);
- }
-
- // build.UBR, such as 17763.557
- if (m_release.UBR != 0) {
- DWORD build = 0;
-
- if (compatibilityMode()) {
- build = m_real.build;
- } else {
- build = m_reported.build;
- }
-
- sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR));
- }
-
- // release ID
- if (!m_release.ID.isEmpty()) {
- sl.push_back("release " + m_release.ID);
- }
-
- // buildlab string
- if (!m_release.buildLab.isEmpty()) {
- sl.push_back(m_release.buildLab);
- }
-
- // product name
- if (!m_release.productName.isEmpty()) {
- sl.push_back(m_release.productName);
- }
-
- // elevated
- QString elevated = "?";
- if (m_elevated.has_value()) {
- elevated = (*m_elevated ? "yes" : "no");
- }
-
- sl.push_back("elevated: " + elevated);
-
- return sl.join(", ");
-}
-
-WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const
-{
- // windows has been deprecating pretty much all the functions having to do
- // with getting version information because apparently, people keep misusing
- // them for feature detection
- //
- // there's still RtlGetVersion() though
-
- using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW);
-
- auto* RtlGetVersion = reinterpret_cast<RtlGetVersionType*>(
- GetProcAddress(ntdll, "RtlGetVersion"));
-
- if (!RtlGetVersion) {
- qCritical() << "RtlGetVersion() not found in ntdll.dll";
- return {};
- }
-
- OSVERSIONINFOEX vi = {};
- vi.dwOSVersionInfoSize = sizeof(vi);
-
- // this apparently never fails
- RtlGetVersion((RTL_OSVERSIONINFOW*)&vi);
-
- return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber};
-}
-
-WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const
-{
- // getting the actual windows version is more difficult because all the
- // functions are lying when running in compatibility mode
- //
- // RtlGetNtVersionNumbers() is an undocumented function that seems to work
- // fine, but it might not in the future
-
- using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*);
-
- auto* RtlGetNtVersionNumbers = reinterpret_cast<RtlGetNtVersionNumbersType*>(
- GetProcAddress(ntdll, "RtlGetNtVersionNumbers"));
-
- if (!RtlGetNtVersionNumbers) {
- qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll";
- return {};
- }
-
- DWORD major=0, minor=0, build=0;
- RtlGetNtVersionNumbers(&major, &minor, &build);
-
- // for whatever reason, the build number has 0xf0000000 set
- build = 0x0fffffff & build;
-
- return {major, minor, build};
-}
-
-WindowsInfo::Release WindowsInfo::getRelease() const
-{
- // there are several interesting items in the registry, but most of them
- // are undocumented, not always available, and localizable
- //
- // most of them are used to provide as much information as possible in case
- // any of the other versions fail to work
-
- QSettings settings(
- R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
- QSettings::NativeFormat);
-
- Release r;
-
- // buildlab seems to be an internal name from the build system
- r.buildLab = settings.value("BuildLabEx", "").toString();
- if (r.buildLab.isEmpty()) {
- r.buildLab = settings.value("BuildLab", "").toString();
- if (r.buildLab.isEmpty()) {
- r.buildLab = settings.value("BuildBranch", "").toString();
- }
- }
-
- // localized name of windows, such as "Windows 10 Pro"
- r.productName = settings.value("ProductName", "").toString();
-
- // release ID, such as 1803
- r.ID = settings.value("ReleaseId", "").toString();
-
- // some other build number, shown in winver.exe
- r.UBR = settings.value("UBR", 0).toUInt();
-
- return r;
-}
-
-std::optional<bool> WindowsInfo::getElevated() const
-{
- HandlePtr token;
-
- {
- HANDLE rawToken = 0;
-
- if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) {
- const auto e = GetLastError();
-
- qCritical()
- << "while trying to check if process is elevated, "
- << "OpenProcessToken() failed: " << formatSystemMessageQ(e);
-
- return {};
- }
-
- token.reset(rawToken);
- }
-
- TOKEN_ELEVATION e = {};
- DWORD size = sizeof(TOKEN_ELEVATION);
-
- if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) {
- const auto e = GetLastError();
-
- qCritical()
- << "while trying to check if process is elevated, "
- << "GetTokenInformation() failed: " << formatSystemMessageQ(e);
-
- return {};
- }
-
- return (e.TokenIsElevated != 0);
-}
-
-
-SecurityProduct::SecurityProduct(
- QString name, int provider,
- bool active, bool upToDate) :
- m_name(std::move(name)), m_provider(provider),
- m_active(active), m_upToDate(upToDate)
-{
-}
-
-const QString& SecurityProduct::name() const
-{
- return m_name;
-}
-
-int SecurityProduct::provider() const
-{
- return m_provider;
-}
-
-bool SecurityProduct::active() const
-{
- return m_active;
-}
-
-bool SecurityProduct::upToDate() const
-{
- return m_upToDate;
-}
-
-QString SecurityProduct::toString() const
-{
- QString s;
-
- s += m_name + " ";
-
-
- QStringList ps;
- if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) {
- ps.push_back("firewall");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) {
- ps.push_back("autoupdate");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) {
- ps.push_back("antivirus");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) {
- ps.push_back("antispyware");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) {
- ps.push_back("settings");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) {
- ps.push_back("uac");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) {
- ps.push_back("service");
- }
-
- if (ps.empty()) {
- s += "(doesn't provide anything)";
- } else {
- s += "(" + ps.join("|") + ")";
- }
-
- if (m_active) {
- s += ", active";
- } else {
- s += ", inactive";
- }
-
- if (!m_upToDate) {
- s += ", definitions outdated";
- }
-
- return s;
-}
-
-
-struct Process
-{
- std::wstring filename;
- DWORD pid;
-
- Process(std::wstring f, DWORD id)
- : filename(std::move(f)), pid(id)
- {
- }
-};
-
-// returns the filename of the given process or the current one
-//
-std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- DWORD bufferSize = MAX_PATH;
-
- for (int tries=0; tries<MaxTries; ++tries)
- {
- auto buffer = std::make_unique<wchar_t[]>(bufferSize + 1);
- std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0);
-
- DWORD writtenSize = 0;
-
- if (process == INVALID_HANDLE_VALUE) {
- // query this process
- writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize);
- } else {
- // query another process
- writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize);
- }
-
- if (writtenSize == 0) {
- // hard failure
- const auto e = GetLastError();
- std::wcerr << formatSystemMessage(e) << L"\n";
- break;
- } else if (writtenSize >= bufferSize) {
- // buffer is too small, try again
- bufferSize *= 2;
- } else {
- // if GetModuleFileName() works, `writtenSize` does not include the null
- // terminator
- const std::wstring s(buffer.get(), writtenSize);
- const fs::path path(s);
-
- return path.filename().native();
- }
- }
-
- // something failed or the path is way too long to make sense
-
- std::wstring what;
- if (process == INVALID_HANDLE_VALUE) {
- what = L"the current process";
- } else {
- what = L"pid " + std::to_wstring(reinterpret_cast<std::uintptr_t>(process));
- }
-
- std::wcerr << L"failed to get filename for " << what << L"\n";
- return {};
-}
-
-std::vector<DWORD> runningProcessesIds()
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- // initial size of 300 processes, unlikely to be more than that
- std::size_t size = 300;
-
- for (int tries=0; tries<MaxTries; ++tries) {
- auto ids = std::make_unique<DWORD[]>(size);
- std::fill(ids.get(), ids.get() + size, 0);
-
- DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0]));
- DWORD bytesWritten = 0;
-
- if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten))
- {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to enumerate processes, "
- << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- if (bytesWritten == bytesGiven) {
- // no way to distinguish between an exact fit and not enough space,
- // just try again
- size *= 2;
- continue;
- }
-
- const auto count = bytesWritten / sizeof(ids[0]);
- return std::vector<DWORD>(ids.get(), ids.get() + count);
- }
-
- std::cerr << L"too many processes to enumerate";
- return {};
-}
-
-std::vector<Process> runningProcesses()
-{
- const auto pids = runningProcessesIds();
- std::vector<Process> v;
-
- for (const auto& pid : pids) {
- if (pid == 0) {
- // the idle process has pid 0 and seems to be picked up by EnumProcesses()
- continue;
- }
-
- HandlePtr h(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!h) {
- const auto e = GetLastError();
-
- if (e != ERROR_ACCESS_DENIED) {
- // don't log access denied, will happen a lot for system processes, even
- // when elevated
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
- }
-
- continue;
- }
-
- auto filename = processFilename(h.get());
- if (!filename.empty()) {
- v.emplace_back(std::move(filename), pid);
- }
- }
-
- return v;
-}
-
-DWORD findOtherPid()
-{
- const std::wstring defaultName = L"ModOrganizer.exe";
-
- std::wclog << L"looking for the other process...\n";
-
- // used to skip the current process below
- const auto thisPid = GetCurrentProcessId();
- std::wclog << L"this process id is " << thisPid << L"\n";
-
- // getting the filename for this process, assumes the other process has the
- // smae one
- auto filename = processFilename();
- if (filename.empty()) {
- std::wcerr
- << L"can't get current process filename, defaulting to "
- << defaultName << L"\n";
-
- filename = defaultName;
- } else {
- std::wclog << L"this process filename is " << filename << L"\n";
- }
-
- // getting all running processes
- const auto processes = runningProcesses();
- std::wclog << L"there are " << processes.size() << L" processes running\n";
-
- // going through processes, trying to find one with the same name and a
- // different pid than this process has
- for (const auto& p : processes) {
- if (p.filename == filename) {
- if (p.pid != thisPid) {
- return p.pid;
- }
- }
- }
-
- std::wclog
- << L"no process with this filename\n"
- << L"MO may not be running, or it may be running as administrator\n"
- << L"you can try running this again as administrator\n";
-
- return 0;
-}
-
-std::wstring tempDir()
-{
- const DWORD bufferSize = MAX_PATH + 1;
- wchar_t buffer[bufferSize + 1] = {};
-
- const auto written = GetTempPathW(bufferSize, buffer);
- if (written == 0) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to get temp path, " << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- // `written` does not include the null terminator
- return std::wstring(buffer, buffer + written);
-}
-
-HandlePtr tempFile(const std::wstring dir)
-{
- // maximum tries of incrementing the counter
- const int MaxTries = 100;
-
- // UTC time and date will be in the filename
- const auto now = std::time(0);
- const auto tm = std::gmtime(&now);
-
- // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where
- // i can go until MaxTries
- std::wostringstream oss;
- oss
- << L"ModOrganizer-"
- << std::setw(4) << (1900 + tm->tm_year)
- << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1)
- << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T"
- << std::setw(2) << std::setfill(L'0') << tm->tm_hour
- << std::setw(2) << std::setfill(L'0') << tm->tm_min
- << std::setw(2) << std::setfill(L'0') << tm->tm_sec;
-
- const std::wstring prefix = oss.str();
- const std::wstring ext = L".dmp";
-
- // first path to try, without counter in it
- std::wstring path = dir + L"\\" + prefix + ext;
-
- for (int i=0; i<MaxTries; ++i) {
- std::wclog << L"trying file '" << path << L"'\n";
-
- HandlePtr h (CreateFileW(
- path.c_str(), GENERIC_WRITE, 0, nullptr,
- CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
-
- if (h.get() != INVALID_HANDLE_VALUE) {
- // worked
- return h;
- }
-
- const auto e = GetLastError();
-
- if (e != ERROR_FILE_EXISTS) {
- // probably no write access
- std::wcerr
- << L"failed to create dump file, " << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- // try again with "-i"
- path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext;
- }
-
- std::wcerr << L"can't create dump file, ran out of filenames\n";
- return {};
-}
-
-HandlePtr dumpFile()
-{
- // try the current directory
- HandlePtr h = tempFile(L".");
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
-
- std::wclog << L"cannot write dump file in current directory\n";
-
- // try the temp directory
- const auto dir = tempDir();
-
- if (!dir.empty()) {
- h = tempFile(dir.c_str());
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
- }
-
- return {};
-}
-
-bool createMiniDump(HANDLE process, CoreDumpTypes type)
-{
- const DWORD pid = GetProcessId(process);
-
- const HandlePtr file = dumpFile();
- if (!file) {
- std::wcerr << L"nowhere to write the dump file\n";
- return false;
- }
-
- auto flags = _MINIDUMP_TYPE(
- MiniDumpNormal |
- MiniDumpWithHandleData |
- MiniDumpWithUnloadedModules |
- MiniDumpWithProcessThreadData);
-
- if (type == CoreDumpTypes::Data) {
- std::wclog << L"writing minidump with data\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs);
- } else if (type == CoreDumpTypes::Full) {
- std::wclog << L"writing full minidump\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory);
- } else {
- std::wclog << L"writing mini minidump\n";
- }
-
- const auto ret = MiniDumpWriteDump(
- process, pid, file.get(), flags, nullptr, nullptr, nullptr);
-
- if (!ret) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- std::wclog << L"minidump written correctly\n";
- return true;
-}
-
-
-bool coredump(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for the current process\n";
- return createMiniDump(GetCurrentProcess(), type);
-}
-
-bool coredumpOther(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for an running process\n";
-
- const auto pid = findOtherPid();
- if (pid == 0) {
- std::wcerr << L"no other process found\n";
- return false;
- }
-
- std::wclog << L"found other process with pid " << pid << L"\n";
-
- HandlePtr handle(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!handle) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- return createMiniDump(handle.get(), type);
-}
-
-} // namespace env
-
} // namespace MOShared
diff --git a/src/shared/util.h b/src/shared/util.h
index c4a2ed7d..7bae96f2 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -20,13 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef UTIL_H
#define UTIL_H
-
#include <string>
-#include <optional>
-
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
#include <versioninfo.h>
class Executable;
@@ -50,394 +44,6 @@ std::wstring ToLower(const std::wstring &text);
bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs);
-
-namespace env
-{
-
-// an application shortcut that can be either on the desktop or the start menu
-//
-class Shortcut
-{
-public:
- // location of a shortcut
- //
- enum Locations
- {
- None = 0,
-
- // on the desktop
- Desktop,
-
- // in the start menu
- StartMenu
- };
-
-
- // empty shortcut
- //
- Shortcut();
-
- // shortcut from an executable
- //
- explicit Shortcut(const Executable& exe);
-
- // sets the name of the shortcut, shown on icons and start menu entries
- //
- Shortcut& name(const QString& s);
-
- // the program to start
- //
- Shortcut& target(const QString& s);
-
- // arguments to pass
- //
- Shortcut& arguments(const QString& s);
-
- // shows in the status bar of explorer, for example
- //
- Shortcut& description(const QString& s);
-
- // path to a binary that contains the icon and its index
- //
- Shortcut& icon(const QString& s, int index=0);
-
- // "start in" option for this shortcut
- //
- Shortcut& workingDirectory(const QString& s);
-
-
- // returns whether this shortcut already exists at the given location; this
- // does not check whether the shortcut parameters are different, it merely if
- // the .lnk file exists
- //
- bool exists(Locations loc) const;
-
- // calls remove() if exists(), or add()
- //
- bool toggle(Locations loc);
-
- // adds the shortcut to the given location
- //
- bool add(Locations loc);
-
- // removes the shortcut from the given location
- //
- bool remove(Locations loc);
-
-private:
- QString m_name;
- QString m_target;
- QString m_arguments;
- QString m_description;
- QString m_icon;
- int m_iconIndex;
- QString m_workingDirectory;
-
- // returns a qCritical() logger with a prefix already logged
- //
- QDebug critical() const;
-
- // returns a qDebug() logger with a prefix already logged
- //
- QDebug debug() const;
-
-
- // returns the path where the shortcut file should be saved
- //
- QString shortcutPath(Locations loc) const;
-
- // returns the directory where the shortcut file should be saved
- //
- QString shortcutDirectory(Locations loc) const;
-
- // returns the filename of the shortcut file that should be used when saving
- //
- QString shortcutFilename() const;
-};
-
-
-// returns a string representation of the given location
-//
-QString toString(Shortcut::Locations loc);
-
-
-// represents one module
-//
-class Module
-{
-public:
- explicit Module(QString path, std::size_t fileSize);
-
- // returns the module's path
- //
- const QString& path() const;
-
- // returns the module's path in lowercase and using forward slashes
- //
- QString displayPath() const;
-
- // returns the size in bytes, may be 0
- //
- std::size_t fileSize() const;
-
- // returns the x.x.x.x version embedded from the version info, may be empty
- //
- const QString& version() const;
-
- // returns the FileVersion entry from the resource file, returns
- // "(no version)" if not available
- //
- const QString& versionString() const;
-
- // returns the build date from the version info, or the creation time of the
- // file on the filesystem, may be empty
- //
- const QDateTime& timestamp() const;
-
- // returns the md5 of the file, may be empty for system files
- //
- const QString& md5() const;
-
- // converts timestamp() to a string for display, returns "(no timestamp)" if
- // not available
- //
- QString timestampString() const;
-
- // returns a string with all the above information on one line
- //
- QString toString() const;
-
-private:
- // contains the information from the version resource
- //
- struct FileInfo
- {
- VS_FIXEDFILEINFO ffi;
- QString fileDescription;
- };
-
- QString m_path;
- std::size_t m_fileSize;
- QString m_version;
- QDateTime m_timestamp;
- QString m_versionString;
- QString m_md5;
-
- // returns information from the version resource
- //
- FileInfo getFileInfo() const;
-
- // uses VS_FIXEDFILEINFO to build the version string
- //
- QString getVersion(const VS_FIXEDFILEINFO& fi) const;
-
- // uses the file date from VS_FIXEDFILEINFO if available, or gets the
- // creation date on the file
- //
- QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const;
-
- // returns the md5 hash unless the path contains "\windows\"
- //
- QString getMD5() const;
-
- // gets VS_FIXEDFILEINFO from the file version info buffer
- //
- VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const;
-
- // gets FileVersion from the file version info buffer
- //
- QString getFileDescription(std::byte* buffer) const;
-};
-
-
-// a variety of information on windows
-//
-class WindowsInfo
-{
-public:
- struct Version
- {
- DWORD major=0, minor=0, build=0;
-
- QString toString() const
- {
- return QString("%1.%2.%3").arg(major).arg(minor).arg(build);
- }
-
- friend bool operator==(const Version& a, const Version& b)
- {
- return
- a.major == b.major &&
- a.minor == b.minor &&
- a.build == b.build;
- }
-
- friend bool operator!=(const Version& a, const Version& b)
- {
- return !(a == b);
- }
- };
-
- struct Release
- {
- // the BuildLab entry from the registry, may be empty
- QString buildLab;
-
- // product name such as "Windows 10 Pro", may not be in English, may be
- // empty
- QString productName;
-
- // release ID such as 1809, may be mepty
- QString ID;
-
- // some sub-build number, undocumented, may be empty
- DWORD UBR;
-
- Release()
- : UBR(0)
- {
- }
- };
-
-
- WindowsInfo();
-
- // tries to guess whether this process is running in compatibility mode
- //
- bool compatibilityMode() const;
-
- // returns the Windows version, may not correspond to the actual version
- // if the process is running in compatibility mode
- //
- const Version& reportedVersion() const;
-
- // tries to guess the real Windows version that's running, can be empty
- //
- const Version& realVersion() const;
-
- // various information about the current release
- //
- const Release& release() const;
-
- // whether this process is running as administrator, may be empty if the
- // information is not available
- std::optional<bool> isElevated() const;
-
- // returns a string with all the above information on one line
- //
- QString toString() const;
-
-private:
- Version m_reported, m_real;
- Release m_release;
- std::optional<bool> m_elevated;
-
- // uses RtlGetVersion() to get the version number as reported by Windows
- //
- Version getReportedVersion(HINSTANCE ntdll) const;
-
- // uses RtlGetNtVersionNumbers() to get the real version number
- //
- Version getRealVersion(HINSTANCE ntdll) const;
-
- // gets various information from the registry
- //
- Release getRelease() const;
-
- // gets whether the process is elevated
- //
- std::optional<bool> getElevated() const;
-};
-
-
-// represents a security product, such as an antivirus or a firewall
-//
-class SecurityProduct
-{
-public:
- SecurityProduct(
- QString name, int provider,
- bool active, bool upToDate);
-
- // display name of the product
- //
- const QString& name() const;
-
- // a bunch of _WSC_SECURITY_PROVIDER flags
- //
- int provider() const;
-
- // whether the product is active
- //
- bool active() const;
-
- // whether its definitions are up-to-date
- //
- bool upToDate() const;
-
- // string representation of the above
- //
- QString toString() const;
-
-private:
- QString m_name;
- int m_provider;
- bool m_active;
- bool m_upToDate;
-};
-
-
-// represents the process's environment
-//
-class Environment
-{
-public:
- Environment();
-
- // list of loaded modules in the current process
- //
- const std::vector<Module>& loadedModules();
-
- // information about the operating system
- //
- const WindowsInfo& windowsInfo() const;
-
- // information about the installed security products
- //
- const std::vector<SecurityProduct>& securityProducts() const;
-
-private:
- std::vector<Module> m_modules;
- WindowsInfo m_windows;
- std::vector<SecurityProduct> m_security;
-
- std::vector<Module> getLoadedModules() const;
- std::vector<SecurityProduct> getSecurityProducts() const;
-
- std::vector<SecurityProduct> getSecurityProductsFromWMI() const;
- std::optional<SecurityProduct> getWindowsFirewall() const;
-};
-
-
-enum class CoreDumpTypes
-{
- Mini = 1,
- Data,
- Full
-};
-
-// creates a minidump file for the given process
-//
-bool coredump(CoreDumpTypes type);
-
-// finds another process with the same name as this one and creates a minidump
-// file for it
-//
-bool coredumpOther(CoreDumpTypes type);
-
-} // namespace env
-
-
MOBase::VersionInfo createVersionInfo();
} // namespace MOShared
diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp
index 4ee4716e..b1643b2d 100644
--- a/src/syncoverwritedialog.cpp
+++ b/src/syncoverwritedialog.cpp
@@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "ui_syncoverwritedialog.h"
#include <utility.h>
#include <report.h>
+#include <log.h>
#include <QDir>
#include <QDirIterator>
@@ -86,7 +87,7 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director
if (subDir != nullptr) {
readTree(fileInfo.absoluteFilePath(), subDir, newItem);
} else {
- qCritical("no directory structure for %s?", qUtf8Printable(file));
+ log::error("no directory structure for {}?", file);
delete newItem;
newItem = nullptr;
}
diff --git a/src/texteditor.cpp b/src/texteditor.cpp
index 130cd76f..0c0eb1cc 100644
--- a/src/texteditor.cpp
+++ b/src/texteditor.cpp
@@ -1,7 +1,10 @@
#include "texteditor.h"
#include "utility.h"
+#include <log.h>
#include <QSplitter>
+using namespace MOBase;
+
TextEditor::TextEditor(QWidget* parent) :
QPlainTextEdit(parent),
m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr),
@@ -249,7 +252,7 @@ QWidget* TextEditor::wrapEditWidget()
auto index = splitter->indexOf(this);
if (index == -1) {
- qCritical(
+ log::error(
"TextEditor: cannot wrap edit widget to display a toolbar, "
"parent is a splitter, but widget isn't in it");
@@ -260,7 +263,7 @@ QWidget* TextEditor::wrapEditWidget()
} else {
// unknown parent
- qCritical(
+ log::error(
"TextEditor: cannot wrap edit widget to display a toolbar, "
"no parent or parent has no layout");
diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp
index 451f56d4..6b8f7bd0 100644
--- a/src/transfersavesdialog.cpp
+++ b/src/transfersavesdialog.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "isavegame.h"
#include "savegameinfo.h"
#include <utility.h>
+#include <log.h>
#include <QtDebug>
#include <QDateTime>
@@ -186,7 +187,7 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked()
[this](const QString &source, const QString &destination) -> bool {
return shellMove(source, destination, this);
},
- "Failed to move %s to %s")) {
+ "Failed to move {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -203,7 +204,7 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked()
[this](const QString &source, const QString &destination) -> bool {
return shellCopy(source, destination, this);
},
- "Failed to copy %s to %s")) {
+ "Failed to copy {} to {}")) {
refreshLocalSaves();
refreshLocalCharacters();
}
@@ -218,7 +219,7 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked()
[this](const QString &source, const QString &destination) -> bool {
return shellMove(source, destination, this);
},
- "Failed to move %s to %s")) {
+ "Failed to move {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -235,7 +236,7 @@ void TransferSavesDialog::on_copyToGlobalBtn_clicked()
[this](const QString &source, const QString &destination) -> bool {
return shellCopy(source, destination, this);
},
- "Failed to copy %s to %s")) {
+ "Failed to copy {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
}
@@ -340,9 +341,7 @@ bool TransferSavesDialog::transferCharacters(
}
if (!method(sourceFile.absoluteFilePath(), destinationFile)) {
- qCritical(errmsg,
- sourceFile.absoluteFilePath().toUtf8().constData(),
- qUtf8Printable(destinationFile));
+ log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile);
}
}
}
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index b752667d..b5e6edb1 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -32,7 +32,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <qstandardpaths.h>
static const char SHMID[] = "mod_organizer_instance";
-
+using namespace MOBase;
std::string to_hex(void *bufferIn, size_t bufferSize)
{
@@ -60,8 +60,7 @@ LogWorker::LogWorker()
"yyyy-MM-dd_hh-mm-ss")))
{
m_LogFile.open(QIODevice::WriteOnly);
- qDebug("usvfs log messages are written to %s",
- qUtf8Printable(m_LogFile.fileName()));
+ log::debug("usvfs log messages are written to {}", m_LogFile.fileName());
}
LogWorker::~LogWorker()
@@ -90,15 +89,16 @@ void LogWorker::exit()
m_QuitRequested = true;
}
-LogLevel logLevel(int level)
+LogLevel toUsvfsLogLevel(log::Levels level)
{
- switch (static_cast<LogLevel>(level)) {
- case LogLevel::Info:
+ switch (level) {
+ case log::Info:
return LogLevel::Info;
- case LogLevel::Warning:
+ case log::Warning:
return LogLevel::Warning;
- case LogLevel::Error:
+ case log::Error:
return LogLevel::Error;
+ case log::Debug: // fall-through
default:
return LogLevel::Debug;
}
@@ -118,17 +118,67 @@ CrashDumpsType crashDumpsType(int type)
}
}
+QString toString(LogLevel lv)
+{
+ switch (lv)
+ {
+ case LogLevel::Debug:
+ return "debug";
+
+ case LogLevel::Info:
+ return "info";
+
+ case LogLevel::Warning:
+ return "warning";
+
+ case LogLevel::Error:
+ return "error";
+
+ default:
+ return QString("%1").arg(static_cast<int>(lv));
+ }
+}
+
+QString toString(CrashDumpsType t)
+{
+ switch (t)
+ {
+ case CrashDumpsType::None:
+ return "none";
+
+ case CrashDumpsType::Mini:
+ return "mini";
+
+ case CrashDumpsType::Data:
+ return "data";
+
+ case CrashDumpsType::Full:
+ return "full";
+
+ default:
+ return QString("%1").arg(static_cast<int>(t));
+ }
+}
+
UsvfsConnector::UsvfsConnector()
{
USVFSParameters params;
- LogLevel level = logLevel(Settings::instance().logLevel());
+ LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel());
CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType());
std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true);
USVFSInitParameters(&params, SHMID, false, level, dumpType, dumpPath.c_str());
InitLogging(false);
- qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath);
+ log::debug(
+ "initializing usvfs:\n"
+ " . instance: {}\n"
+ " . log: {}\n"
+ " . dump: {} ({})",
+ params.instanceName,
+ toString(params.logLevel),
+ params.crashDumpsPath,
+ toString(params.crashDumpsType));
CreateVFS(&params);
@@ -167,7 +217,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
int files = 0;
int dirs = 0;
- qDebug("Updating VFS mappings...");
+ log::debug("Updating VFS mappings...");
ClearVirtualMappings();
@@ -195,19 +245,13 @@ void UsvfsConnector::updateMapping(const MappingType &mapping)
}
}
- qDebug("VFS mappings updated <linked %d dirs, %d files>", dirs, files);
- /*
- size_t dumpSize = 0;
- CreateVFSDump(nullptr, &dumpSize);
- std::unique_ptr<char[]> buffer(new char[dumpSize]);
- CreateVFSDump(buffer.get(), &dumpSize);
- qDebug(buffer.get());
- */
+ log::debug("VFS mappings updated <linked {} dirs, {} files>", dirs, files);
}
-void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist)
+void UsvfsConnector::updateParams(
+ MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist)
{
- USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType));
+ USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType));
ClearExecutableBlacklist();
for (auto exec : executableBlacklist.split(";")) {
std::wstring buf = exec.toStdWString();
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
index 8a88bde5..b0bd320c 100644
--- a/src/usvfsconnector.h
+++ b/src/usvfsconnector.h
@@ -29,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDebug>
#include <QList>
#include <usvfsparameters.h>
+#include <log.h>
#include "executableinfo.h"
@@ -84,7 +85,11 @@ public:
~UsvfsConnector();
void updateMapping(const MappingType &mapping);
- void updateParams(int logLevel, int crashDumpsType, QString executableBlacklist);
+
+ void updateParams(
+ MOBase::log::Levels logLevel, int crashDumpsType,
+ QString executableBlacklist);
+
void updateForcedLibraries(const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries);
private: