mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa0cb9446 | ||
|
|
0657774e2e | ||
|
|
695593968b | ||
|
|
61edeb12f1 | ||
|
|
db6b536fb1 | ||
|
|
64590a10d7 | ||
|
|
4a582e524d | ||
|
|
a00cda607c | ||
|
|
42407866e9 | ||
|
|
c3a8a0b9eb | ||
|
|
607b2a10aa | ||
|
|
6b60d54853 | ||
|
|
c7101be7d8 | ||
|
|
d80a77a4cb | ||
|
|
33b544112c | ||
|
|
8a3af93b39 | ||
|
|
a625acd17b | ||
|
|
f5a815185f |
+3
-1
@@ -669,7 +669,9 @@ if qt_env['QT_MAJOR_VERSION'] > 4:
|
||||
dlls_to_install += [
|
||||
os.path.join(env['QTDIR'],
|
||||
'bin',
|
||||
'icu%s%d%d.dll' % (lib, qt_env['QT_MAJOR_VERSION'], qt_env['QT_MINOR_VERSION'] - 1))
|
||||
'icu%s%d%d.dll' % (lib,
|
||||
qt_env['QT_MAJOR_VERSION'],
|
||||
qt_env['QT_MINOR_VERSION'] - 1))
|
||||
for lib in ('dt','in', 'uc')
|
||||
]
|
||||
|
||||
|
||||
+13
-10
@@ -35,7 +35,7 @@ removing = None
|
||||
includes = dict()
|
||||
|
||||
adding = None
|
||||
added = dict()
|
||||
added = defaultdict(list)
|
||||
lcadded = dict()
|
||||
|
||||
foundline = None
|
||||
@@ -70,10 +70,10 @@ def process_next_line(line, outfile):
|
||||
elif adding:
|
||||
m = re.match(r'.*class (.*);', line)
|
||||
if m:
|
||||
added[m.group(1)] = (adding, line)
|
||||
added[m.group(1)].append((adding, line))
|
||||
lcadded[m.group(1).lower() + '.h'] = m.group(1)
|
||||
else:
|
||||
added[line] = (adding, line)
|
||||
added[line].append((adding, line))
|
||||
elif removing:
|
||||
# Really we should stash these so that if we get a 'class xxx' in
|
||||
# the add lines we can print it here. also we could do the case
|
||||
@@ -88,10 +88,12 @@ def process_next_line(line, outfile):
|
||||
if clname in lcadded:
|
||||
clname = lcadded[clname]
|
||||
if clname in added:
|
||||
messages[removing].append(
|
||||
'%s(%s) : warning I0004: Replace include of %s with '
|
||||
'forward reference %s' % (
|
||||
removing, m.group(2), m.group(1), added[clname][1]))
|
||||
for item in added[clname]:
|
||||
if removing == item[0]:
|
||||
messages[removing].append(
|
||||
'%s(%s) : warning I0004: Replace include of %s'
|
||||
' with forward reference %s' % (
|
||||
removing, m.group(2), m.group(1), item[1]))
|
||||
del added[clname]
|
||||
else:
|
||||
messages[removing].append(
|
||||
@@ -140,9 +142,10 @@ if foundline is None:
|
||||
foundline = '1'
|
||||
|
||||
for add in added:
|
||||
messages[added[add][0]].append(
|
||||
'%s(%s) : warning I0003: Need to include %s' % (
|
||||
added[add][0], foundline, added[add][1]))
|
||||
for item in added[add]:
|
||||
messages[item[0]].append(
|
||||
'%s(%s) : warning I0003: Need to include %s' % (
|
||||
item[0], foundline, item[1]))
|
||||
|
||||
for file in sorted(messages.keys(), reverse = True):
|
||||
for line in messages[file]:
|
||||
|
||||
@@ -44,6 +44,10 @@ QString CategoryFactory::categoriesFilePath()
|
||||
CategoryFactory::CategoryFactory()
|
||||
{
|
||||
atexit(&cleanup);
|
||||
}
|
||||
|
||||
void CategoryFactory::loadCategories()
|
||||
{
|
||||
reset();
|
||||
|
||||
QFile categoryFile(categoriesFilePath());
|
||||
|
||||
@@ -75,6 +75,11 @@ public:
|
||||
**/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief read categories from file
|
||||
*/
|
||||
void loadCategories();
|
||||
|
||||
/**
|
||||
* @brief save the categories to the categories.dat file
|
||||
**/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
|
||||
<file name="icuin53.dll"/>
|
||||
<file name="icuuc53.dll"/>
|
||||
<file name="icudt53.dll"/>
|
||||
<file name="icuin54.dll"/>
|
||||
<file name="icuuc54.dll"/>
|
||||
<file name="icudt54.dll"/>
|
||||
<file name="Qt5Cored.dll"/>
|
||||
<file name="Qt5Declaratived.dll"/>
|
||||
<file name="Qt5Guid.dll"/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
|
||||
<file name="icuin53.dll"/>
|
||||
<file name="icuuc53.dll"/>
|
||||
<file name="icudt53.dll"/>
|
||||
<file name="icuin54.dll"/>
|
||||
<file name="icuuc54.dll"/>
|
||||
<file name="icudt54.dll"/>
|
||||
<file name="libeay32.dll"/>
|
||||
<file name="ssleay32.dll"/>
|
||||
<file name="Qt5Core.dll"/>
|
||||
|
||||
@@ -39,6 +39,7 @@ IGameInfo::Type GameInfoImpl::type() const
|
||||
case GameInfo::TYPE_FALLOUT4: return IGameInfo::TYPE_FALLOUT4;
|
||||
case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV;
|
||||
case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM;
|
||||
case GameInfo::TYPE_SKYRIMSE: return IGameInfo::TYPE_SKYRIMSE;
|
||||
default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,16 +77,14 @@ static T resolveFunction(QLibrary &lib, const char *name)
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
||||
InstallationManager::InstallationManager()
|
||||
: m_ParentWidget(nullptr)
|
||||
, m_SupportedExtensions({ "zip", "rar", "7z", "fomod", "001" })
|
||||
{
|
||||
QLibrary archiveLib(QCoreApplication::applicationDirPath()
|
||||
+ "\\dlls\\archive.dll");
|
||||
: m_ParentWidget(nullptr),
|
||||
m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}) {
|
||||
QLibrary archiveLib(QCoreApplication::applicationDirPath() +
|
||||
"\\dlls\\archive.dll");
|
||||
if (!archiveLib.load()) {
|
||||
throw MyException(
|
||||
tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
|
||||
throw MyException(QObject::tr("archive.dll not loaded: \"%1\"")
|
||||
.arg(archiveLib.errorString()));
|
||||
}
|
||||
|
||||
CreateArchiveType CreateArchiveFunc
|
||||
|
||||
@@ -72,7 +72,7 @@ QString InstanceManager::queryInstanceName() const
|
||||
// would be neat if we could take the names from the game plugins but
|
||||
// the required initialization order requires the ini file to be
|
||||
// available *before* we load plugins
|
||||
dialog.setComboBoxItems({ "Oblivion", "Skyrim", "Fallout 3",
|
||||
dialog.setComboBoxItems({ "Oblivion", "Skyrim", "SkyrimSE", "Fallout 3",
|
||||
"Fallout NV", "Fallout 4" });
|
||||
dialog.setComboBoxEditable(true);
|
||||
dialog.setWindowTitle(QObject::tr("Enter Instance Name"));
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
virtual void lock() = 0;
|
||||
virtual void unlock() = 0;
|
||||
virtual bool unlockClicked() = 0;
|
||||
virtual void setProcessName(QString const &) = 0;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -19,8 +19,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "lockeddialog.h"
|
||||
#include "ui_lockeddialog.h"
|
||||
#include <QResizeEvent>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QResizeEvent>
|
||||
#include <QWidget>
|
||||
#include <Qt> // for Qt::FramelessWindowHint, etc
|
||||
|
||||
LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton)
|
||||
: QDialog(parent)
|
||||
|
||||
+10
-1
@@ -20,7 +20,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#ifndef LOCKEDDIALOG_H
|
||||
#define LOCKEDDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialog> // for QDialog
|
||||
#include <QObject> // for Q_OBJECT, slots
|
||||
#include <QString> // for QString
|
||||
|
||||
class QResizeEvent;
|
||||
class QWidget;
|
||||
|
||||
namespace Ui {
|
||||
class LockedDialog;
|
||||
@@ -49,6 +54,10 @@ public:
|
||||
**/
|
||||
bool unlockClicked() const { return m_UnlockClicked; }
|
||||
|
||||
/**
|
||||
* @brief set the name of the process being run
|
||||
* @param name of process
|
||||
*/
|
||||
void setProcessName(const QString &name);
|
||||
|
||||
protected:
|
||||
|
||||
+18
-33
@@ -85,38 +85,21 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
using namespace MOBase;
|
||||
using namespace MOShared;
|
||||
|
||||
|
||||
bool createAndMakeWritable(const std::wstring &subPath)
|
||||
{
|
||||
bool createAndMakeWritable(const std::wstring &subPath) {
|
||||
QString const dataPath = qApp->property("dataPath").toString();
|
||||
QString fullPath = dataPath + "/" + QString::fromStdWString(subPath);
|
||||
|
||||
if (!QDir(fullPath).exists()) {
|
||||
QDir().mkdir(fullPath);
|
||||
if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) {
|
||||
QMessageBox::critical(nullptr, QObject::tr("Error"),
|
||||
QObject::tr("Failed to create \"%1\". Your user "
|
||||
"account probably lacks permission.")
|
||||
.arg(fullPath));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(fullPath);
|
||||
if (!fileInfo.exists() || !fileInfo.isWritable()) {
|
||||
if (QMessageBox::question(nullptr, QObject::tr("Permissions required"),
|
||||
QObject::tr("The current user account doesn't have the required access rights to run "
|
||||
"Mod Organizer. The neccessary changes can be made automatically (the MO directory "
|
||||
"will be made writable for the current user account). You will be asked to run "
|
||||
"\"helper.exe\" with administrative rights."),
|
||||
QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
|
||||
if (!Helper::init(qApp->applicationDirPath().toStdWString(),
|
||||
dataPath.toStdWString())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// no matter which directory didn't exist/wasn't writable, the helper
|
||||
// should have created them all so we don't have to worry this message box would appear repeatedly
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool bootstrap()
|
||||
{
|
||||
// remove the temporary backup directory in case we're restarting after an update
|
||||
@@ -129,11 +112,9 @@ bool bootstrap()
|
||||
removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
|
||||
"usvfs*.log", 5, QDir::Name);
|
||||
|
||||
createAndMakeWritable(AppConfig::profilesPath());
|
||||
createAndMakeWritable(AppConfig::modsPath());
|
||||
createAndMakeWritable(AppConfig::downloadPath());
|
||||
createAndMakeWritable(AppConfig::overwritePath());
|
||||
createAndMakeWritable(AppConfig::logPath());
|
||||
if (!createAndMakeWritable(AppConfig::logPath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -168,7 +149,7 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except
|
||||
if (QMessageBox::question(nullptr, QObject::tr("Woops"),
|
||||
QObject::tr("ModOrganizer has crashed! "
|
||||
"Should a diagnostic file be created? "
|
||||
"If you send me this file (%1) to sherb@gmx.net, "
|
||||
"If you send me this file (%1) to modorganizer@gmail.com, "
|
||||
"the bug is a lot more likely to be fixed. "
|
||||
"Please include a short description of what you were "
|
||||
"doing when the crash happened"
|
||||
@@ -445,7 +426,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
qDebug("data path: %s", qPrintable(dataPath));
|
||||
|
||||
if (!bootstrap()) {
|
||||
reportError("failed to set up data path");
|
||||
reportError("failed to set up data paths");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -465,6 +446,10 @@ int runApplication(MOApplication &application, SingleInstance &instance,
|
||||
QSettings::IniFormat);
|
||||
qDebug("initializing core");
|
||||
OrganizerCore organizer(settings);
|
||||
if (!organizer.bootstrap()) {
|
||||
reportError("failed to set up data paths");
|
||||
return 1;
|
||||
}
|
||||
qDebug("initialize plugins");
|
||||
PluginContainer pluginContainer(&organizer);
|
||||
pluginContainer.loadPlugins();
|
||||
|
||||
+77
-27
@@ -34,6 +34,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "organizercore.h"
|
||||
#include "pluginlistsortproxy.h"
|
||||
#include "previewgenerator.h"
|
||||
#include "serverinfo.h"
|
||||
#include "savegameinfo.h"
|
||||
#include "spawn.h"
|
||||
#include "versioninfo.h"
|
||||
@@ -200,8 +201,11 @@ MainWindow::MainWindow(QSettings &initSettings
|
||||
{
|
||||
ui->setupUi(this);
|
||||
updateWindowTitle(QString(), false);
|
||||
|
||||
languageChange(m_OrganizerCore.settings().language());
|
||||
|
||||
m_CategoryFactory.loadCategories();
|
||||
|
||||
ui->logList->setModel(LogBuffer::instance());
|
||||
ui->logList->setColumnWidth(0, 100);
|
||||
ui->logList->setAutoScroll(true);
|
||||
@@ -1008,8 +1012,14 @@ void MainWindow::startExeAction()
|
||||
{
|
||||
QAction *action = qobject_cast<QAction*>(sender());
|
||||
if (action != nullptr) {
|
||||
m_OrganizerCore.spawnBinary(
|
||||
const Executable &selectedExecutable(
|
||||
m_OrganizerCore.executablesList()->find(action->text()));
|
||||
m_OrganizerCore.spawnBinary(
|
||||
selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
|
||||
selectedExecutable.m_WorkingDirectory.length() != 0
|
||||
? selectedExecutable.m_WorkingDirectory
|
||||
: selectedExecutable.m_BinaryInfo.absolutePath(),
|
||||
selectedExecutable.m_SteamAppID);
|
||||
} else {
|
||||
qCritical("not an action?");
|
||||
}
|
||||
@@ -1447,18 +1457,31 @@ void MainWindow::storeSettings(QSettings &settings) {
|
||||
|
||||
void MainWindow::lock()
|
||||
{
|
||||
if (m_LockDialog != nullptr) {
|
||||
++m_LockCount;
|
||||
return;
|
||||
}
|
||||
m_LockDialog = new LockedDialog(qApp->activeWindow());
|
||||
m_LockDialog->show();
|
||||
setEnabled(false);
|
||||
m_LockDialog->setEnabled(true); //What's the point otherwise?
|
||||
++m_LockCount;
|
||||
}
|
||||
|
||||
void MainWindow::unlock()
|
||||
{
|
||||
if (m_LockDialog != nullptr) {
|
||||
//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");
|
||||
return;
|
||||
}
|
||||
--m_LockCount;
|
||||
if (m_LockCount == 0) {
|
||||
m_LockDialog->hide();
|
||||
m_LockDialog->deleteLater();
|
||||
m_LockDialog = nullptr;
|
||||
setEnabled(true);
|
||||
}
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
bool MainWindow::unlockClicked()
|
||||
@@ -1470,6 +1493,13 @@ bool MainWindow::unlockClicked()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setProcessName(QString const &name)
|
||||
{
|
||||
if (m_LockDialog != nullptr) {
|
||||
m_LockDialog->setProcessName(name);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_btnRefreshData_clicked()
|
||||
{
|
||||
m_OrganizerCore.refreshDirectoryStructure();
|
||||
@@ -1512,13 +1542,17 @@ void MainWindow::installMod(QString fileName)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_startButton_clicked() {
|
||||
const Executable &selectedExecutable(getSelectedExecutable());
|
||||
|
||||
void MainWindow::on_startButton_clicked()
|
||||
{
|
||||
m_OrganizerCore.spawnBinary(getSelectedExecutable());
|
||||
m_OrganizerCore.spawnBinary(
|
||||
selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
|
||||
selectedExecutable.m_WorkingDirectory.length() != 0
|
||||
? selectedExecutable.m_WorkingDirectory
|
||||
: selectedExecutable.m_BinaryInfo.absolutePath(),
|
||||
selectedExecutable.m_SteamAppID);
|
||||
}
|
||||
|
||||
|
||||
static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
|
||||
LPCSTR linkFileName, LPCWSTR description,
|
||||
LPCTSTR iconFileName, int iconNumber,
|
||||
@@ -3188,42 +3222,58 @@ void MainWindow::linkMenu()
|
||||
|
||||
void MainWindow::on_actionSettings_triggered()
|
||||
{
|
||||
QString oldModDirectory(m_OrganizerCore.settings().getModDirectory());
|
||||
QString oldCacheDirectory(m_OrganizerCore.settings().getCacheDirectory());
|
||||
bool oldDisplayForeign(m_OrganizerCore.settings().displayForeign());
|
||||
bool proxy = m_OrganizerCore.settings().useProxy();
|
||||
m_OrganizerCore.settings().query(this);
|
||||
m_OrganizerCore.installationManager()->setModsDirectory(m_OrganizerCore.settings().getModDirectory());
|
||||
m_OrganizerCore.installationManager()->setDownloadDirectory(m_OrganizerCore.settings().getDownloadDirectory());
|
||||
Settings &settings = m_OrganizerCore.settings();
|
||||
|
||||
QString oldModDirectory(settings.getModDirectory());
|
||||
QString oldCacheDirectory(settings.getCacheDirectory());
|
||||
QString oldProfilesDirectory(settings.getProfileDirectory());
|
||||
bool oldDisplayForeign(settings.displayForeign());
|
||||
bool proxy = settings.useProxy();
|
||||
|
||||
settings.query(this);
|
||||
|
||||
InstallationManager *instManager = m_OrganizerCore.installationManager();
|
||||
instManager->setModsDirectory(settings.getModDirectory());
|
||||
instManager->setDownloadDirectory(settings.getDownloadDirectory());
|
||||
|
||||
fixCategories();
|
||||
refreshFilters();
|
||||
if (QDir::fromNativeSeparators(m_OrganizerCore.downloadManager()->getOutputDirectory()) != QDir::fromNativeSeparators(m_OrganizerCore.settings().getDownloadDirectory())) {
|
||||
if (m_OrganizerCore.downloadManager()->downloadsInProgress()) {
|
||||
MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this);
|
||||
|
||||
if (settings.getProfileDirectory() != oldProfilesDirectory) {
|
||||
refreshProfiles();
|
||||
}
|
||||
|
||||
DownloadManager *dlManager = m_OrganizerCore.downloadManager();
|
||||
|
||||
if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) {
|
||||
if (dlManager->downloadsInProgress()) {
|
||||
MessageDialog::showMessage(tr("Can't change download directory while "
|
||||
"downloads are in progress!"),
|
||||
this);
|
||||
} else {
|
||||
m_OrganizerCore.downloadManager()->setOutputDirectory(m_OrganizerCore.settings().getDownloadDirectory());
|
||||
dlManager->setOutputDirectory(settings.getDownloadDirectory());
|
||||
}
|
||||
}
|
||||
m_OrganizerCore.downloadManager()->setPreferredServers(m_OrganizerCore.settings().getPreferredServers());
|
||||
dlManager->setPreferredServers(settings.getPreferredServers());
|
||||
|
||||
if ((m_OrganizerCore.settings().getModDirectory() != oldModDirectory)
|
||||
|| (m_OrganizerCore.settings().displayForeign() != oldDisplayForeign)) {
|
||||
if ((settings.getModDirectory() != oldModDirectory)
|
||||
|| (settings.displayForeign() != oldDisplayForeign)) {
|
||||
m_OrganizerCore.profileRefresh();
|
||||
}
|
||||
|
||||
if (m_OrganizerCore.settings().getCacheDirectory() != oldCacheDirectory) {
|
||||
NexusInterface::instance()->setCacheDirectory(m_OrganizerCore.settings().getCacheDirectory());
|
||||
if (settings.getCacheDirectory() != oldCacheDirectory) {
|
||||
NexusInterface::instance()->setCacheDirectory(settings.getCacheDirectory());
|
||||
}
|
||||
|
||||
if (proxy != m_OrganizerCore.settings().useProxy()) {
|
||||
activateProxy(m_OrganizerCore.settings().useProxy());
|
||||
if (proxy != settings.useProxy()) {
|
||||
activateProxy(settings.useProxy());
|
||||
}
|
||||
|
||||
NexusInterface::instance()->setNMMVersion(m_OrganizerCore.settings().getNMMVersion());
|
||||
NexusInterface::instance()->setNMMVersion(settings.getNMMVersion());
|
||||
|
||||
updateDownloadListDelegate();
|
||||
|
||||
m_OrganizerCore.setLogLevel(m_OrganizerCore.settings().logLevel());
|
||||
m_OrganizerCore.setLogLevel(settings.logLevel());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ public:
|
||||
virtual void lock() override;
|
||||
virtual void unlock() override;
|
||||
virtual bool unlockClicked() override;
|
||||
virtual void setProcessName(QString const &name) override;
|
||||
|
||||
bool addProfile();
|
||||
void refreshDataTree();
|
||||
@@ -350,6 +351,7 @@ private:
|
||||
bool m_DidUpdateMasterList;
|
||||
|
||||
LockedDialog *m_LockDialog { nullptr };
|
||||
uint64_t m_LockCount { 0 };
|
||||
|
||||
std::vector<std::pair<QString, QHeaderView*>> m_PersistedGeometry;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "modinfooverwrite.h"
|
||||
|
||||
#include "appconfig.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDirIterator>
|
||||
@@ -21,7 +22,7 @@ bool ModInfoOverwrite::isEmpty() const
|
||||
|
||||
QString ModInfoOverwrite::absolutePath() const
|
||||
{
|
||||
return QDir::fromNativeSeparators(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath()));
|
||||
return Settings::instance().getOverwriteDirectory();
|
||||
}
|
||||
|
||||
std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const
|
||||
|
||||
@@ -252,12 +252,12 @@ bool NexusInterface::isURLGameRelated(const QUrl &url) const
|
||||
|
||||
QString NexusInterface::getGameURL() const
|
||||
{
|
||||
return "http://www.nexusmods.com/" + m_Game->gameShortName().toLower();
|
||||
return "http://www.nexusmods.com/" + m_Game->gameNexusName().toLower();
|
||||
}
|
||||
|
||||
QString NexusInterface::getOldModsURL() const
|
||||
{
|
||||
return "http://" + m_Game->gameShortName().toLower() + ".nexusmods.com/mods";
|
||||
return "http://" + m_Game->gameNexusName().toLower() + ".nexusmods.com/mods";
|
||||
}
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ void NexusInterface::managedGameChanged(IPluginGame const *game)
|
||||
namespace {
|
||||
QString get_management_url(MOBase::IPluginGame const *game)
|
||||
{
|
||||
return "http://nmm.nexusmods.com/" + game->gameShortName().toLower();
|
||||
return "http://nmm.nexusmods.com/" + game->gameNexusName().toLower();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+424
-402
File diff suppressed because it is too large
Load Diff
+138
-161
@@ -1,17 +1,22 @@
|
||||
#include "organizercore.h"
|
||||
|
||||
#include "delayedfilewriter.h"
|
||||
#include "guessedvalue.h"
|
||||
#include "imodinterface.h"
|
||||
#include "imoinfo.h"
|
||||
#include "iplugingame.h"
|
||||
#include "iuserinterface.h"
|
||||
#include "loadmechanism.h"
|
||||
#include "messagedialog.h"
|
||||
#include "modlistsortproxy.h"
|
||||
#include "modrepositoryfileinfo.h"
|
||||
#include "nexusinterface.h"
|
||||
#include "plugincontainer.h"
|
||||
#include "pluginlistsortproxy.h"
|
||||
#include "profile.h"
|
||||
#include "logbuffer.h"
|
||||
#include "credentialsdialog.h"
|
||||
#include "filedialogmemory.h"
|
||||
#include "lockeddialog.h"
|
||||
#include "modinfodialog.h"
|
||||
#include "spawn.h"
|
||||
#include "syncoverwritedialog.h"
|
||||
@@ -28,22 +33,33 @@
|
||||
#include <questionboxmemory.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkInterface>
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include <QtDebug>
|
||||
#include <QtGlobal> // for qPrintable, etc
|
||||
|
||||
#include <Psapi.h>
|
||||
#include <tchar.h> // for _tcsicmp
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h> // for memset, wcsrchr
|
||||
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string> //for wstring
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -586,6 +602,25 @@ InstallationManager *OrganizerCore::installationManager()
|
||||
return &m_InstallationManager;
|
||||
}
|
||||
|
||||
bool OrganizerCore::createDirectory(const QString &path) {
|
||||
if (!QDir(path).exists() && !QDir().mkpath(path)) {
|
||||
QMessageBox::critical(nullptr, QObject::tr("Error"),
|
||||
QObject::tr("Failed to create \"%1\". Your user "
|
||||
"account probably lacks permission.")
|
||||
.arg(QDir::toNativeSeparators(path)));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool OrganizerCore::bootstrap() {
|
||||
return createDirectory(m_Settings.getProfileDirectory()) &&
|
||||
createDirectory(m_Settings.getModDirectory()) &&
|
||||
createDirectory(m_Settings.getDownloadDirectory()) &&
|
||||
createDirectory(m_Settings.getOverwriteDirectory());
|
||||
}
|
||||
|
||||
void OrganizerCore::createDefaultProfile()
|
||||
{
|
||||
QString profilesPath = settings().getProfileDirectory();
|
||||
@@ -610,7 +645,19 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
|
||||
&& (profileName == m_CurrentProfile->name())) {
|
||||
return;
|
||||
}
|
||||
QString profileDir = settings().getProfileDirectory() + "/" + profileName;
|
||||
|
||||
QDir profileBaseDir(settings().getProfileDirectory());
|
||||
QString profileDir = profileBaseDir.absoluteFilePath(profileName);
|
||||
|
||||
if (!QDir(profileDir).exists()) {
|
||||
// selected profile doesn't exist. Ensure there is at least one profile,
|
||||
// then pick any one
|
||||
createDefaultProfile();
|
||||
|
||||
profileDir = profileBaseDir.absoluteFilePath(
|
||||
profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0));
|
||||
}
|
||||
|
||||
Profile *newProfile = new Profile(QDir(profileDir), managedGame());
|
||||
|
||||
delete m_CurrentProfile;
|
||||
@@ -974,120 +1021,34 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
|
||||
return res;
|
||||
}
|
||||
|
||||
void OrganizerCore::spawnBinary(const Executable &exe)
|
||||
void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite)
|
||||
{
|
||||
spawnBinary(
|
||||
exe.m_BinaryInfo, exe.m_Arguments,
|
||||
exe.m_WorkingDirectory.length() != 0 ? exe.m_WorkingDirectory
|
||||
: exe.m_BinaryInfo.absolutePath(),
|
||||
exe.m_SteamAppID,
|
||||
m_CurrentProfile->setting("custom_overwrites", exe.m_Title).toString());
|
||||
}
|
||||
|
||||
void OrganizerCore::spawnBinary(const QFileInfo &binary,
|
||||
const QString &arguments,
|
||||
const QDir ¤tDirectory,
|
||||
const QString &steamAppID,
|
||||
const QString &customOverwrite)
|
||||
{
|
||||
LockedDialog *dialog = new LockedDialog(qApp->activeWindow());
|
||||
dialog->show();
|
||||
ON_BLOCK_EXIT([&]() {
|
||||
dialog->hide();
|
||||
dialog->deleteLater();
|
||||
if (m_UserInterface != nullptr) {
|
||||
m_UserInterface->lock();
|
||||
}
|
||||
ON_BLOCK_EXIT([&] () {
|
||||
if (m_UserInterface != nullptr) { m_UserInterface->unlock(); }
|
||||
});
|
||||
|
||||
HANDLE processHandle
|
||||
= spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(),
|
||||
currentDirectory, steamAppID, customOverwrite);
|
||||
HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite);
|
||||
if (processHandle != INVALID_HANDLE_VALUE) {
|
||||
if (m_UserInterface != nullptr) {
|
||||
m_UserInterface->setWindowEnabled(false);
|
||||
}
|
||||
// re-enable the locked dialog because what'd be the point otherwise?
|
||||
dialog->setEnabled(true);
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
DWORD processExitCode;
|
||||
(void)waitForProcessCompletion(processHandle, &processExitCode);
|
||||
|
||||
dialog->setProcessName(
|
||||
QString::fromStdWString(getProcessName(::GetProcessId(processHandle))));
|
||||
|
||||
{
|
||||
DWORD currentProcess = 0UL;
|
||||
|
||||
DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000,
|
||||
QS_KEY | QS_MOUSE);
|
||||
bool tryAgain = true;
|
||||
while ((res != WAIT_FAILED) && !dialog->unlockClicked()) {
|
||||
if (res == WAIT_OBJECT_0) {
|
||||
// process ended, is there another one in the group?
|
||||
static const DWORD maxCount = 5;
|
||||
size_t numProcesses = maxCount;
|
||||
LPDWORD processes = new DWORD[maxCount];
|
||||
if (::GetVFSProcessList(&numProcesses, processes)) {
|
||||
bool found = false;
|
||||
size_t count =
|
||||
std::min<size_t>(static_cast<size_t>(maxCount), numProcesses);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::wstring processName = getProcessName(processes[i]);
|
||||
if (!boost::starts_with(processName, L"ModOrganizer.exe")) {
|
||||
currentProcess = processes[i];
|
||||
dialog->setProcessName(QString::fromStdWString(processName));
|
||||
processHandle
|
||||
= ::OpenProcess(SYNCHRONIZE, FALSE, currentProcess);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// it's possible the previous process has deregistered before
|
||||
// the new one has registered, so we should try one more time
|
||||
// with a little delay
|
||||
if (tryAgain) {
|
||||
tryAgain = false;
|
||||
QThread::msleep(500);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tryAgain = true;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// keep processing events so the app doesn't appear dead
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000,
|
||||
QS_KEY | QS_MOUSE);
|
||||
}
|
||||
::GetExitCodeProcess(processHandle, &processExitCode);
|
||||
|
||||
::CloseHandle(processHandle);
|
||||
|
||||
if (m_UserInterface != nullptr) {
|
||||
m_UserInterface->setWindowEnabled(true);
|
||||
}
|
||||
// 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");
|
||||
QFile::remove(m_CurrentProfile->getLoadOrderFileName());
|
||||
}
|
||||
refreshDirectoryStructure();
|
||||
|
||||
refreshESPList();
|
||||
savePluginList();
|
||||
|
||||
m_FinishedRun(binary.absoluteFilePath(), processExitCode);
|
||||
refreshDirectoryStructure();
|
||||
// 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");
|
||||
QFile::remove(m_CurrentProfile->getLoadOrderFileName());
|
||||
}
|
||||
refreshDirectoryStructure();
|
||||
|
||||
refreshESPList();
|
||||
savePluginList();
|
||||
|
||||
//These callbacks should not fiddle with directoy structure and ESPs.
|
||||
m_FinishedRun(binary.absoluteFilePath(), processExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,12 +1088,11 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
|
||||
if ((window != nullptr) && (!window->isVisible())) {
|
||||
window = nullptr;
|
||||
}
|
||||
if (QuestionBoxMemory::query(window, "steamQuery", tr("Start Steam?"),
|
||||
tr("Steam is required to be running already "
|
||||
"to correctly start the game. "
|
||||
"Should MO try to start steam now?"),
|
||||
QDialogButtonBox::Yes | QDialogButtonBox::No)
|
||||
== QDialogButtonBox::Yes) {
|
||||
if (QuestionBoxMemory::query(window, "steamQuery", binary.fileName(),
|
||||
tr("Start Steam?"),
|
||||
tr("Steam is required to be running already to correctly start the game. "
|
||||
"Should MO try to start steam now?"),
|
||||
QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) {
|
||||
startSteam(qApp->activeWindow());
|
||||
}
|
||||
}
|
||||
@@ -1148,6 +1108,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
|
||||
m_CurrentProfile->modlistWriter().writeImmediately(true);
|
||||
}
|
||||
|
||||
// TODO: should also pass arguments
|
||||
if (m_AboutToRun(binary.absoluteFilePath())) {
|
||||
try {
|
||||
m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
|
||||
@@ -1256,67 +1217,83 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
|
||||
{
|
||||
if (m_UserInterface != nullptr) {
|
||||
m_UserInterface->lock();
|
||||
ON_BLOCK_EXIT([&]() { m_UserInterface->unlock(); });
|
||||
}
|
||||
|
||||
DWORD retLen;
|
||||
JOBOBJECT_BASIC_PROCESS_ID_LIST info;
|
||||
ON_BLOCK_EXIT([&] () {
|
||||
if (m_UserInterface != nullptr) {
|
||||
m_UserInterface->unlock();
|
||||
} });
|
||||
return waitForProcessCompletion(handle, exitCode);
|
||||
}
|
||||
|
||||
bool isJobHandle = true;
|
||||
|
||||
ULONG lastProcessID = ULONG_MAX;
|
||||
bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode)
|
||||
{
|
||||
HANDLE processHandle = handle;
|
||||
|
||||
DWORD res
|
||||
= ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE);
|
||||
static const DWORD maxCount = 5;
|
||||
size_t numProcesses = maxCount;
|
||||
LPDWORD processes = new DWORD[maxCount];
|
||||
|
||||
DWORD currentProcess = 0UL;
|
||||
bool tryAgain = true;
|
||||
|
||||
DWORD res;
|
||||
// Wait for a an event on the handle, a key press, mouse click or timeout
|
||||
while (
|
||||
(res != WAIT_FAILED) && (res != WAIT_OBJECT_0)
|
||||
&& ((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked())) {
|
||||
if (isJobHandle) {
|
||||
if (::QueryInformationJobObject(handle, JobObjectBasicProcessIdList,
|
||||
&info, sizeof(info), &retLen)
|
||||
> 0) {
|
||||
if (info.NumberOfProcessIdsInList == 0) {
|
||||
// fake signaled state
|
||||
res = WAIT_OBJECT_0;
|
||||
break;
|
||||
} else {
|
||||
// this is indeed a job handle. Figure out one of the process handles
|
||||
// as well.
|
||||
if (lastProcessID != info.ProcessIdList[0]) {
|
||||
lastProcessID = info.ProcessIdList[0];
|
||||
if (processHandle != handle) {
|
||||
::CloseHandle(processHandle);
|
||||
}
|
||||
processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
|
||||
lastProcessID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the info-object I passed only provides space for 1 process id. but
|
||||
// since this code only cares about whether there
|
||||
// is more than one that's good enough. ERROR_MORE_DATA simply signals
|
||||
// there are at least two processes running.
|
||||
// any other error probably means the handle is a regular process
|
||||
// handle, probably caused by running MO in a job without
|
||||
// the right to break out.
|
||||
if (::GetLastError() != ERROR_MORE_DATA) {
|
||||
isJobHandle = false;
|
||||
}
|
||||
res = ::MsgWaitForMultipleObjects(1, &handle, false, 500,
|
||||
QS_KEY | QS_MOUSE),
|
||||
(MOBase::isOneOf(res, {WAIT_FAILED, WAIT_OBJECT_0}) &&
|
||||
((m_UserInterface == nullptr) || !m_UserInterface->unlockClicked()))) {
|
||||
|
||||
if (!::GetVFSProcessList(&numProcesses, processes)) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
size_t count =
|
||||
std::min<size_t>(static_cast<size_t>(maxCount), numProcesses);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::wstring processName = getProcessName(processes[i]);
|
||||
if (!boost::starts_with(processName, L"ModOrganizer.exe")) {
|
||||
currentProcess = processes[i];
|
||||
m_UserInterface->setProcessName(QString::fromStdWString(processName));
|
||||
processHandle = ::OpenProcess(SYNCHRONIZE, FALSE, currentProcess);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// it's possible the previous process has deregistered before
|
||||
// the new one has registered, so we should try one more time
|
||||
// with a little delay
|
||||
if (tryAgain) {
|
||||
tryAgain = false;
|
||||
QThread::msleep(500);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tryAgain = true;
|
||||
}
|
||||
|
||||
// keep processing events so the app doesn't appear dead
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
res = ::MsgWaitForMultipleObjects(1, &handle, false, 500,
|
||||
QS_KEY | QS_MOUSE);
|
||||
}
|
||||
|
||||
if (exitCode != nullptr) {
|
||||
::GetExitCodeProcess(processHandle, exitCode);
|
||||
//This is actually wrong if the process we started finished before we
|
||||
//got the event and so we end up with a job handle.
|
||||
if (! ::GetExitCodeProcess(processHandle, exitCode))
|
||||
{
|
||||
DWORD error = ::GetLastError();
|
||||
qDebug() << "Failed to get process exit code: Error " << error;
|
||||
}
|
||||
}
|
||||
|
||||
::CloseHandle(processHandle);
|
||||
if (handle != processHandle) {
|
||||
::CloseHandle(handle);
|
||||
}
|
||||
|
||||
return res == WAIT_OBJECT_0;
|
||||
}
|
||||
@@ -1870,7 +1847,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
|
||||
|
||||
result.insert(result.end(), {
|
||||
QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()),
|
||||
QDir::toNativeSeparators(game->dataDirectory().absolutePath()),
|
||||
dataPath,
|
||||
true,
|
||||
customOverwrite.isEmpty()
|
||||
});
|
||||
|
||||
+23
-7
@@ -23,21 +23,28 @@
|
||||
class ModListSortProxy;
|
||||
class PluginListSortProxy;
|
||||
class Profile;
|
||||
namespace MOBase { template <typename T> class GuessedValue; }
|
||||
namespace MOBase {
|
||||
template <typename T> class GuessedValue;
|
||||
class IModInterface;
|
||||
}
|
||||
namespace MOShared { class DirectoryEntry; }
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QThread>
|
||||
#include <QVariant>
|
||||
|
||||
class QNetworkReply;
|
||||
class QUrl;
|
||||
class QWidget;
|
||||
|
||||
#include <Windows.h> //for HANDLE, LPDWORD
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
@@ -129,12 +136,22 @@ public:
|
||||
|
||||
void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
|
||||
|
||||
void spawnBinary(const Executable &exe);
|
||||
HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite);
|
||||
void spawnBinary(const QFileInfo &binary, const QString &arguments = "",
|
||||
const QDir ¤tDirectory = QDir(),
|
||||
const QString &steamAppID = "",
|
||||
const QString &customOverwrite = "");
|
||||
|
||||
HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments,
|
||||
const QString &profileName,
|
||||
const QDir ¤tDirectory,
|
||||
const QString &steamAppID,
|
||||
const QString &customOverwrite);
|
||||
|
||||
void loginSuccessfulUpdate(bool necessary);
|
||||
void loginFailedUpdate(const QString &message);
|
||||
|
||||
static bool createAndMakeWritable(const QString &path);
|
||||
bool bootstrap();
|
||||
void createDefaultProfile();
|
||||
|
||||
MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; }
|
||||
@@ -228,6 +245,8 @@ private:
|
||||
|
||||
bool testForSteam();
|
||||
|
||||
bool createDirectory(const QString &path);
|
||||
|
||||
/**
|
||||
* @brief return a descriptor of the mappings real file->virtual file
|
||||
*/
|
||||
@@ -240,10 +259,7 @@ private:
|
||||
const MOShared::DirectoryEntry *directoryEntry,
|
||||
int createDestination);
|
||||
|
||||
void spawnBinary(const QFileInfo &binary, const QString &arguments = "",
|
||||
const QDir ¤tDirectory = QDir(),
|
||||
const QString &steamAppID = "",
|
||||
const QString &customOverwrite = "");
|
||||
bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode);
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user