summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTannin <devnull@localhost>2014-04-05 15:14:37 +0200
committerTannin <devnull@localhost>2014-04-05 15:14:37 +0200
commitc017f4a0d50b67a44e276bd5ae8929ed3990c62c (patch)
treebe504af55ffc99b657eca9938a4a76864e41454f /src
parentb1f1682790072fbfb45bd9eaa3c6890edfb81a22 (diff)
- added buttons to backup and restore the modlist and pluginlist
- replaced boss integration with loot
Diffstat (limited to 'src')
-rw-r--r--src/ModOrganizer.pro3
-rw-r--r--src/main.cpp23
-rw-r--r--src/mainwindow.cpp204
-rw-r--r--src/mainwindow.h16
-rw-r--r--src/mainwindow.ui112
-rw-r--r--src/modlist.cpp2
-rw-r--r--src/pluginlist.cpp224
-rw-r--r--src/pluginlist.h83
-rw-r--r--src/profile.h6
-rw-r--r--src/resources.qrc3
-rw-r--r--src/resources/arrange-boxes.pngbin0 -> 1634 bytes
-rw-r--r--src/resources/document-save_32.pngbin0 -> 1971 bytes
-rw-r--r--src/resources/edit-undo.pngbin0 -> 1601 bytes
-rw-r--r--src/selectiondialog.cpp123
-rw-r--r--src/selectiondialog.h96
-rw-r--r--src/shared/gameinfo.cpp8
-rw-r--r--src/shared/gameinfo.h1
-rw-r--r--src/spawn.cpp29
-rw-r--r--src/spawn.h8
19 files changed, 486 insertions, 455 deletions
diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro
index 05b05855..62de3f66 100644
--- a/src/ModOrganizer.pro
+++ b/src/ModOrganizer.pro
@@ -14,7 +14,8 @@ SUBDIRS = bsatk \
BossDummy \
pythonRunner \
boss_modified \
- esptk
+ esptk \
+ loot_cli
plugins.depends = pythonRunner
hookdll.depends = shared
diff --git a/src/main.cpp b/src/main.cpp
index ac903615..d1c5e263 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -80,24 +80,6 @@ using namespace MOBase;
using namespace MOShared;
-void removeOldLogfiles()
-{
- QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"),
- QDir::Files, QDir::Name);
-
- if (files.count() > 5) {
- QStringList deleteFiles;
- for (int i = 0; i < files.count() - 5; ++i) {
- deleteFiles.append(files.at(i).absoluteFilePath());
- }
-
- if (!shellDelete(deleteFiles)) {
- qWarning("failed to remove log files: %s", qPrintable(windowsErrorString(::GetLastError())));
- }
- }
-}
-
-
// set up required folders (for a first install or after an update or to fix a broken installation)
bool bootstrap()
{
@@ -111,7 +93,7 @@ bool bootstrap()
}
// cycle logfile
- removeOldLogfiles();
+ removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name);
// create organizer directories
QString dirNames[] = {
@@ -120,8 +102,7 @@ bool bootstrap()
QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())),
QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())),
QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())),
- QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())),
- QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss")
+ QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir()))
};
static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index bf5ea7f0..87360f3c 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -109,8 +109,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QtConcurrentRun>
#endif
#include <QCoreApplication>
+#include <QProgressDialog>
#include <scopeguard.h>
#include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
#ifdef TEST_MODELS
@@ -2649,7 +2651,6 @@ void MainWindow::directory_refreshed()
delete oldStructure;
refreshDataTree();
- refreshLists();
} else {
// TODO: don't know why this happens, this slot seems to get called twice with only one emit
return;
@@ -5155,20 +5156,213 @@ void MainWindow::on_showHiddenBox_toggled(bool checked)
m_DownloadManager.setShowHidden(checked);
}
+
+void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite)
+{
+ SECURITY_ATTRIBUTES secAttributes;
+ secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+ secAttributes.bInheritHandle = TRUE;
+ secAttributes.lpSecurityDescriptor = NULL;
+
+ if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) {
+ qCritical("failed to create stdout reroute");
+ }
+
+ if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) {
+ qCritical("failed to correctly set up the stdout reroute");
+ *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE;
+ }
+}
+
+std::string MainWindow::readFromPipe(HANDLE stdOutRead)
+{
+ static const int chunkSize = 128;
+ std::string result;
+
+ char buffer[chunkSize + 1];
+ buffer[chunkSize] = '\0';
+
+ DWORD read = 1;
+ while (read > 0) {
+ if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) {
+ break;
+ }
+ if (read > 0) {
+ result.append(buffer, read);
+ if (read < chunkSize) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
void MainWindow::on_bossButton_clicked()
{
try {
this->setEnabled(false);
ON_BLOCK_EXIT([&] () { this->setEnabled(true); });
- LockedDialog dialog(this, tr("BOSS working"), false);
+ QProgressDialog dialog(this);
+ dialog.setLabelText(tr("LOOT working"));
+ dialog.setMaximum(0);
dialog.show();
- qApp->processEvents();
- m_PluginList.bossSort();
- savePluginList();
+ QStringList parameters;
+ parameters << "--game" << ToQString(GameInfo::instance().getGameName())
+ << "--gamePath" << ToQString(GameInfo::instance().getGameDirectory());
+
+ HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
+ HANDLE stdOutRead = INVALID_HANDLE_VALUE;
+ createStdoutPipe(&stdOutRead, &stdOutWrite);
+
+ HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/lootcli.exe"),
+ parameters.join(" "),
+ m_CurrentProfile->getName(),
+ m_Settings.logLevel(),
+ qApp->applicationDirPath(),
+ true,
+ stdOutWrite);
+
+ // we don't use the write end
+ ::CloseHandle(stdOutWrite);
+
+ if (loot != INVALID_HANDLE_VALUE) {
+ while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) {
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+ if (dialog.wasCanceled()) {
+ ::TerminateProcess(loot, 1);
+ }
+ std::string lootOut = readFromPipe(stdOutRead);
+ std::vector<std::string> lines;
+ boost::split(lines, lootOut, boost::is_any_of("\r\n"));
+ foreach (const std::string &line, lines) {
+ if (line.length() > 0) {
+ size_t progidx = line.find("[progress]");
+ size_t reportidx = line.find("[report]");
+ if (progidx != std::string::npos) {
+ dialog.setLabelText(line.substr(progidx + 11).c_str());
+ } else if (reportidx != std::string::npos) {
+ qDebug("report at %s", line.substr(reportidx + 9).c_str());
+ } else {
+ qDebug("%s", line.c_str());
+ }
+ }
+ }
+ }
+ std::string remainder = readFromPipe(stdOutRead).c_str();
+ if (remainder.length() > 0) {
+ qDebug("%s", remainder.c_str());
+ }
+
+ refreshESPList();
+
+ if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) {
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ }
+
dialog.hide();
} catch (const std::exception &e) {
reportError(tr("failed to run boss: %1").arg(e.what()));
ui->bossButton->setEnabled(false);
}
}
+
+
+const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??";
+const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)";
+const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss";
+
+
+bool MainWindow::createBackup(const QString &filePath, const QDateTime &time)
+{
+ QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE);
+ if (shellCopy(QStringList(filePath), QStringList(outPath), this)) {
+ QFileInfo fileInfo(filePath);
+ removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void MainWindow::on_saveButton_clicked()
+{
+ savePluginList();
+ QDateTime now = QDateTime::currentDateTime();
+ if (createBackup(m_CurrentProfile->getPluginsFileName(), now)
+ && createBackup(m_CurrentProfile->getLoadOrderFileName(), now)
+ && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) {
+ MessageDialog::showMessage(tr("Backup of load order created"), this);
+ }
+}
+
+QString MainWindow::queryRestore(const QString &filePath)
+{
+ QFileInfo pluginFileInfo(filePath);
+ QString pattern = pluginFileInfo.fileName() + ".*";
+ QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name);
+
+ SelectionDialog dialog(tr("Choose backup to restore"), this);
+ QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX);
+ QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)");
+ foreach(const QFileInfo &info, files) {
+ if (exp.exactMatch(info.fileName())) {
+ QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE);
+ dialog.addChoice(time.toString(), "", exp.cap(1));
+ } else if (exp2.exactMatch(info.fileName())) {
+ dialog.addChoice(exp2.cap(1), "", exp2.cap(1));
+ }
+ }
+
+ if (dialog.numChoices() == 0) {
+ QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore"));
+ return QString();
+ }
+
+ if (dialog.exec() == QDialog::Accepted) {
+ return dialog.getChoiceData().toString();
+ } else {
+ return QString();
+ }
+}
+
+void MainWindow::on_restoreButton_clicked()
+{
+ QString pluginName = m_CurrentProfile->getPluginsFileName();
+ QString choice = queryRestore(pluginName);
+ if (!choice.isEmpty()) {
+ QString loadOrderName = m_CurrentProfile->getLoadOrderFileName();
+ QString lockedName = m_CurrentProfile->getLockedOrderFileName();
+ if (!shellCopy(pluginName + "." + choice, pluginName, true, this) ||
+ !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) ||
+ !shellCopy(lockedName + "." + choice, lockedName, true, this)) {
+ QMessageBox::critical(this, tr("Restore failed"),
+ tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError())));
+ }
+ refreshESPList();
+ }
+}
+
+void MainWindow::on_saveModsButton_clicked()
+{
+ m_CurrentProfile->writeModlistNow(true);
+ QDateTime now = QDateTime::currentDateTime();
+ if (createBackup(m_CurrentProfile->getModlistFileName(), now)) {
+ MessageDialog::showMessage(tr("Backup of modlist created"), this);
+ }
+}
+void MainWindow::on_restoreModsButton_clicked()
+{
+ QString modlistName = m_CurrentProfile->getModlistFileName();
+ QString choice = queryRestore(modlistName);
+ if (!choice.isEmpty()) {
+ if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) {
+ QMessageBox::critical(this, tr("Restore failed"),
+ tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError())));
+ }
+ refreshModList(false);
+ }
+}
+
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 23923677..c5b4a8d3 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -154,6 +154,8 @@ public:
void saveArchiveList();
+ void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite);
+ std::string readFromPipe(HANDLE stdOutRead);
public slots:
void displayColumnSelection(const QPoint &pos);
@@ -207,8 +209,6 @@ private:
bool nexusLogin();
- void saveCurrentESPList();
-
bool testForSteam();
void startSteam();
@@ -290,11 +290,18 @@ private:
void activateProxy(bool activate);
void installTranslator(const QString &name);
+ bool createBackup(const QString &filePath, const QDateTime &time);
+ QString queryRestore(const QString &filePath);
+
private:
static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
static const unsigned int PROBLEM_TOOMANYPLUGINS = 2;
+ static const char *PATTERN_BACKUP_GLOB;
+ static const char *PATTERN_BACKUP_REGEX;
+ static const char *PATTERN_BACKUP_DATE;
+
private:
Ui::MainWindow *ui;
@@ -574,6 +581,11 @@ private slots: // ui slots
void on_showHiddenBox_toggled(bool checked);
void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
void on_bossButton_clicked();
+
+ void on_saveButton_clicked();
+ void on_restoreButton_clicked();
+ void on_restoreModsButton_clicked();
+ void on_saveModsButton_clicked();
};
#endif // MAINWINDOW_H
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 26cbbf83..dc91121c 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -115,7 +115,7 @@
<number>2</number>
</property>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
@@ -170,6 +170,47 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restoreModsButton">
+ <property name="toolTip">
+ <string>Restore Backup...</string>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveModsButton">
+ <property name="toolTip">
+ <string>Create Backup</string>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -618,6 +659,68 @@ p, li { white-space: pre-wrap; }
<number>0</number>
</property>
<item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QPushButton" name="bossButton">
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/sort</normaloff>:/MO/gui/sort</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restoreButton">
+ <property name="toolTip">
+ <string>Restore Backup...</string>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/restore</normaloff>:/MO/gui/restore</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveButton">
+ <property name="toolTip">
+ <string>Create Backup</string>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/backup</normaloff>:/MO/gui/backup</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
<widget class="QTreeView" name="espList">
<property name="minimumSize">
<size>
@@ -725,13 +828,6 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
- <item>
- <widget class="QPushButton" name="bossButton">
- <property name="text">
- <string>Sort</string>
- </property>
- </widget>
- </item>
</layout>
</item>
</layout>
diff --git a/src/modlist.cpp b/src/modlist.cpp
index e2cb7cf0..836406e4 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -669,7 +669,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
}
if (source.count() != 0) {
- shellMove(source, target, NULL);
+ shellMove(source, target);
}
return true;
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index af6f42da..1f4ed9b1 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -78,7 +78,6 @@ PluginList::PluginList(QObject *parent)
: QAbstractItemModel(parent)
, m_FontMetrics(QFont())
, m_SaveTimer(this)
- , m_BOSS(NULL)
{
m_SaveTimer.setSingleShot(true);
connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer()));
@@ -95,11 +94,6 @@ PluginList::PluginList(QObject *parent)
PluginList::~PluginList()
{
- if (m_BOSS != NULL) {
- m_BOSS->CleanUpAPI();
- delete m_BOSS;
- m_BOSS = NULL;
- }
}
@@ -595,225 +589,9 @@ void PluginList::refreshLoadOrder()
}
-class boss_exception : public std::runtime_error {
-public:
- boss_exception(const std::string &message) : std::runtime_error(message) {}
-};
-
-#define THROW_BOSS_ERROR(obj) \
- uint8_t *message; \
- obj->GetLastErrorDetails(&message); \
- throw boss_exception(std::string(reinterpret_cast<char*>(message)));
-
-#define U8(text) reinterpret_cast<const uint8_t*>(text)
-
-
-void outputBossLog(const QString &filename)
-{
- QFile file(filename);
- if (file.open(QIODevice::ReadOnly)) {
- while (!file.atEnd()) {
- QByteArray line = file.readLine().trimmed();
- if (line.length() < 1)
- continue;
-
- int endFirstWord = line.indexOf(':');
- if (endFirstWord < 0) {
- endFirstWord = 0;
- }
- QString firstWord = line.mid(0, endFirstWord);
- if (firstWord == "DEBUG") {
- qDebug("(boss) %s", line.mid(endFirstWord + 2).constData());
- } else {
- qWarning("(boss) %s", line.mid(endFirstWord + 2).constData());
- }
- }
- }
- file.resize(0);
-}
-
-
-boss_db PluginList::initBoss()
-{
- boss_db result;
- bool firstRun = (m_BOSS == nullptr);
- if (firstRun) {
- m_BOSS = new BossDLL(TEXT("dlls\\boss.dll"));
-
- if (!m_BOSS->IsCompatibleVersion(2,1,1)) {
- throw MyException(tr("BOSS dll incompatible"));
- }
- uint8_t *versionString;
- if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) {
- THROW_BOSS_ERROR(m_BOSS)
- }
- qDebug("using boss version %s", versionString);
-
- m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name
- m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4);
- }
-
- if (m_BOSS->CreateBossDb(&result, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) {
- uint8_t *message;
- m_BOSS->GetLastErrorDetails(&message);
- std::string messageCopy(reinterpret_cast<const char*>(message));
- delete m_BOSS;
- m_BOSS = NULL;
- throw boss_exception(messageCopy);
- }
- qApp->processEvents();
-
- QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt");
-
- if (firstRun) {
- uint32_t res = m_BOSS->UpdateMasterlist(result, U8(masterlistName.toUtf8().constData()));
- qApp->processEvents();
- if (res == BossDLL::RESULT_OK) {
- qDebug("boss masterlist updated");
- } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) {
- qDebug("boss masterlist already up-to-date");
- } else {
- THROW_BOSS_ERROR(m_BOSS)
- }
- }
- if (m_BOSS->Load(result,
- U8(masterlistName.toUtf8().constData()),
- U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) {
- THROW_BOSS_ERROR(m_BOSS)
- }
- return result;
-}
-
-void PluginList::convertPluginListForBoss(boss_db db, boost::ptr_vector<uint8_t> &inputPlugins, std::vector<uint8_t*> &activePlugins)
-{
- foreach (int idx, m_ESPsByPriority) {
- QString fileName = m_ESPs[idx].m_Name;
- QByteArray name = fileName.toUtf8();
-
- uint8_t *nameU8 = new uint8_t[name.length() + 1];
- memcpy(nameU8, name.constData(), name.length() + 1);
- if (m_ESPs[idx].m_Enabled) {
- activePlugins.push_back(nameU8);
- }
- inputPlugins.push_back(nameU8);
- }
- if (m_BOSS->SetActivePluginsDumb(db, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) {
- THROW_BOSS_ERROR(m_BOSS)
- }
-}
-
-void PluginList::applyBOSSSorting(boss_db db, std::map<int, QString> &lockedLoadOrder, uint8_t **pluginList, size_t size,
- int &priority, int &loadOrder, bool recognized, const char *extension)
-{
- for (size_t i = 0; i < size; ++i) {
- QString name = QString::fromUtf8(reinterpret_cast<const char*>(pluginList[i])).toLower();
- if (name.endsWith(extension)) {
- auto iter = m_ESPsByName.find(name);
- if (iter == m_ESPsByName.end()) {
- // boss seems to report plugins from userlist as sorted that aren't even installed
- continue;
- }
-
- BossMessage *message;
- size_t numMessages = 0;
- m_BOSS->GetPluginMessages(db, pluginList[i], &message, &numMessages);
- BossInfo newInfo;
- for (size_t im = 0; im < numMessages; ++im) {
- newInfo.m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast<const char*>(message[im].message)));
- }
- newInfo.m_BOSSUnrecognized = !recognized;
- m_BossInfo[name] = newInfo;
-
- // locked order plugins are not inserted by boss sorting ...
- if (m_LockedOrder.find(name) != m_LockedOrder.end()) {
- continue;
- }
-
- // ... but by their enforced priority
- while (lockedLoadOrder.find(loadOrder) != lockedLoadOrder.end()) {
- auto lloIter = lockedLoadOrder.find(loadOrder);
- auto nameIter = m_ESPsByName.find(lloIter->second);
- if (nameIter != m_ESPsByName.end()) {
- m_ESPs[nameIter->second].m_Priority = priority++;
- if (m_ESPs[nameIter->second].m_Enabled) {
- m_ESPs[nameIter->second].m_LoadOrder = loadOrder++;
- } else {
- m_ESPs[nameIter->second].m_LoadOrder = -1;
- }
- }
- lockedLoadOrder.erase(lloIter);
- }
-
- m_ESPs[iter->second].m_Priority = priority++;
- if (m_ESPs[iter->second].m_Enabled) {
- m_ESPs[iter->second].m_LoadOrder = loadOrder++;
- } else {
- m_ESPs[iter->second].m_LoadOrder = -1;
- }
- }
- }
-}
-
-void PluginList::bossSort()
+void PluginList::lootSort()
{
- boss_db db = initBoss();
- ON_BLOCK_EXIT([&] { m_BOSS->DestroyBossDb(db); });
-
- // create a boss-compatible representation of our current mod list.
- boost::ptr_vector<uint8_t> inputPlugins;
- std::vector<uint8_t*> activePlugins;
- convertPluginListForBoss(db, inputPlugins, activePlugins);
-
- // sort mods in-memory
- uint8_t **sortedPlugins;
- uint8_t **unrecognizedPlugins;
- size_t sizeSorted, sizeUnrecognized;
- if (m_BOSS->SortCustomMods(db,
- inputPlugins.c_array(), inputPlugins.size(),
- &sortedPlugins, &sizeSorted,
- &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) {
- THROW_BOSS_ERROR(m_BOSS)
- }
-
- // output the log from boss to our own log to make it visible
- outputBossLog(m_TempFile.fileName());
-
- qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized);
- ChangeBracket<PluginList> layoutChange(this);
-
- std::map<int, QString> lockedLoadOrder;
- std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(),
- [&lockedLoadOrder] (const std::pair<QString, int> &ele) { lockedLoadOrder[ele.second] = ele.first; });
-
- int priority = 0;
- int loadOrder = 0;
- applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esm");
- applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esm");
- applyBOSSSorting(db, lockedLoadOrder, sortedPlugins, sizeSorted, priority, loadOrder, true, "esp");
- applyBOSSSorting(db, lockedLoadOrder, unrecognizedPlugins, sizeUnrecognized, priority, loadOrder, false, "esp");
-
- // applyBOSSSorting removed entries from lockedLoadOrder when they were inserted so everything that's left is plugins
- // locked to the end of the list. Now this inserts the rest in the ascending priority order. If the list is too short
- // to place the plugins in their intended position then this guarantees plugins are kept in the correct relative order
- // but it doesn't minimize the number of misplaced plugins.
- for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) {
- auto nameIter = m_ESPsByName.find(iter->second);
- if (nameIter != m_ESPsByName.end()) {
- m_ESPs[nameIter->second].m_Priority = priority++;
- if (m_ESPs[nameIter->second].m_Enabled) {
- m_ESPs[nameIter->second].m_LoadOrder = loadOrder++;
- } else {
- m_ESPs[nameIter->second].m_LoadOrder = -1;
- }
- }
- }
-
- // inform view of the changed data
- updateIndices();
- layoutChange.finish();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
- m_Refreshed();
}
IPluginList::PluginState PluginList::state(const QString &name) const
diff --git a/src/pluginlist.h b/src/pluginlist.h
index fff2a595..5e8557e2 100644
--- a/src/pluginlist.h
+++ b/src/pluginlist.h
@@ -181,7 +181,7 @@ public:
void refreshLoadOrder();
- void bossSort();
+ void lootSort();
public:
virtual PluginState state(const QString &name) const;
@@ -204,7 +204,6 @@ public: // implementation of the QAbstractTableModel interface
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex &child) const;
- void applyBOSSSorting(boss_db db, std::map<int, QString> &lockedLoadOrder, uint8_t **pluginList, size_t size, int &priority, int &loadOrder, bool recognized, const char *extension);
public slots:
/**
@@ -260,82 +259,6 @@ private:
friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS);
friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS);
- class BossDLL : public PDLL {
- DECLARE_CLASS(BossDLL)
-
- DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*)
- DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db)
- DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI)
-
- DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*)
- DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t)
-
- DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*)
-
- DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*)
- DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*)
-
- DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t)
- DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**)
-
- DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**)
- DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t)
-
- DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*)
-
- enum ResultCode {
- RESULT_OK = 0,
- RESULT_NO_MASTER_FILE = 1,
- RESULT_FILE_READ_FAIL = 2,
- RESULT_FILE_WRITE_FAIL = 3,
- RESULT_FILE_NOT_UTF8 = 4,
- RESULT_FILE_NOT_FOUND = 5,
- RESULT_FILE_PARSE_FAIL = 6,
- RESULT_CONDITION_EVAL_FAIL = 7,
- RESULT_REGEX_EVAL_FAIL = 8,
- RESULT_NO_GAME_DETECTED = 9,
- RESULT_ENCODING_CONVERSION_FAIL = 10,
- RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11,
- RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12,
- RESULT_READ_UPDATE_FILE_LIST_FAIL = 13,
- RESULT_FILE_CRC_MISMATCH = 14,
- RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15,
- RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16,
- RESULT_FS_FILE_RENAME_FAIL = 17,
- RESULT_FS_FILE_DELETE_FAIL = 18,
- RESULT_FS_CREATE_DIRECTORY_FAIL = 19,
- RESULT_FS_ITER_DIRECTORY_FAIL = 20,
- RESULT_CURL_INIT_FAIL = 21,
- RESULT_CURL_SET_ERRBUFF_FAIL = 22,
- RESULT_CURL_SET_OPTION_FAIL = 23,
- RESULT_CURL_SET_PROXY_FAIL = 24,
- RESULT_CURL_SET_PROXY_TYPE_FAIL = 25,
- RESULT_CURL_SET_PROXY_AUTH_FAIL = 26,
- RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27,
- RESULT_CURL_PERFORM_FAIL = 28,
- RESULT_CURL_USER_CANCEL = 29,
- RESULT_GUI_WINDOW_INIT_FAIL = 30,
- RESULT_NO_UPDATE_NECESSARY = 31,
- RESULT_LO_MISMATCH = 32,
- RESULT_NO_MEM = 33,
- RESULT_INVALID_ARGS = 34,
- RESULT_NETWORK_FAIL = 35,
- RESULT_NO_INTERNET_CONNECTION = 36,
- RESULT_NO_TAG_MAP = 37,
- RESULT_PLUGINS_FULL = 38,
- RESULT_PLUGIN_BEFORE_MASTER = 39,
- RESULT_INVALID_SYNTAX = 40
- };
-
- enum GameIDs {
- AUTODETECT = 0,
- OBLIVION = 1,
- SKYRIM = 3,
- FALLOUT3 = 4,
- FALLOUTNV = 5
- };
- };
-
private:
void syncLoadOrder();
@@ -353,9 +276,6 @@ private:
void testMasters();
- boss_db initBoss();
- void convertPluginListForBoss(boss_db db, boost::ptr_vector<uint8_t> &inputPlugins, std::vector<uint8_t*> &activePlugins);
-
private:
std::vector<ESPInfo> m_ESPs;
@@ -381,7 +301,6 @@ private:
SignalRefreshed m_Refreshed;
- BossDLL *m_BOSS;
QTemporaryFile m_TempFile;
};
diff --git a/src/profile.h b/src/profile.h
index 4960671a..5aa77357 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -160,6 +160,11 @@ public:
QString getLockedOrderFileName() const;
/**
+ * @return the path of the modlist file in this profile
+ */
+ QString getModlistFileName() const;
+
+ /**
* @return path of the archives file in this profile
*/
QString getArchivesFileName() const;
@@ -301,7 +306,6 @@ private:
void updateIndices();
- QString getModlistFileName() const;
void copyFilesTo(QString &target) const;
std::vector<std::wstring> splitDZString(const wchar_t *buffer) const;
diff --git a/src/resources.qrc b/src/resources.qrc
index 73921f64..bf53ea95 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -52,5 +52,8 @@
<file alias="version_date">resources/x-office-calendar.png</file>
<file alias="warning_16">resources/dialog-warning_16.png</file>
<file alias="attachment">resources/mail-attachment.png</file>
+ <file alias="backup">resources/document-save_32.png</file>
+ <file alias="restore">resources/edit-undo.png</file>
+ <file alias="sort">resources/arrange-boxes.png</file>
</qresource>
</RCC>
diff --git a/src/resources/arrange-boxes.png b/src/resources/arrange-boxes.png
new file mode 100644
index 00000000..b1ab67cf
--- /dev/null
+++ b/src/resources/arrange-boxes.png
Binary files differ
diff --git a/src/resources/document-save_32.png b/src/resources/document-save_32.png
new file mode 100644
index 00000000..db5c52b7
--- /dev/null
+++ b/src/resources/document-save_32.png
Binary files differ
diff --git a/src/resources/edit-undo.png b/src/resources/edit-undo.png
new file mode 100644
index 00000000..61b2ce9a
--- /dev/null
+++ b/src/resources/edit-undo.png
Binary files differ
diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp
index dbc10791..902b4d9c 100644
--- a/src/selectiondialog.cpp
+++ b/src/selectiondialog.cpp
@@ -17,62 +17,67 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "selectiondialog.h"
-#include "ui_selectiondialog.h"
-
-#include <QCommandLinkButton>
-
-SelectionDialog::SelectionDialog(const QString &description, QWidget *parent)
- : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false)
-{
- ui->setupUi(this);
-
- ui->descriptionLabel->setText(description);
-}
-
-SelectionDialog::~SelectionDialog()
-{
- delete ui;
-}
-
-
-void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data)
-{
- QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox);
- button->setProperty("data", data);
- ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole);
- if (data.isValid()) m_ValidateByData = true;
-}
-
-
-QVariant SelectionDialog::getChoiceData()
-{
- return m_Choice->property("data");
-}
-
-
-QString SelectionDialog::getChoiceString()
-{
- if ((m_Choice == NULL) ||
- (m_ValidateByData && !m_Choice->property("data").isValid())) {
- return QString();
- } else {
- return m_Choice->text();
- }
-}
-
-
-void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button)
-{
- m_Choice = button;
- if (!m_ValidateByData || m_Choice->property("data").isValid()) {
- this->accept();
- } else {
- this->reject();
- }
-}
-
-void SelectionDialog::on_cancelButton_clicked()
-{
- this->reject();
-}
+#include "selectiondialog.h"
+#include "ui_selectiondialog.h"
+
+#include <QCommandLinkButton>
+
+SelectionDialog::SelectionDialog(const QString &description, QWidget *parent)
+ : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false)
+{
+ ui->setupUi(this);
+
+ ui->descriptionLabel->setText(description);
+}
+
+SelectionDialog::~SelectionDialog()
+{
+ delete ui;
+}
+
+
+void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data)
+{
+ QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox);
+ button->setProperty("data", data);
+ ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole);
+ if (data.isValid()) m_ValidateByData = true;
+}
+
+int SelectionDialog::numChoices() const
+{
+ return ui->buttonBox->findChildren<QCommandLinkButton*>(QString()).count();
+}
+
+
+QVariant SelectionDialog::getChoiceData()
+{
+ return m_Choice->property("data");
+}
+
+
+QString SelectionDialog::getChoiceString()
+{
+ if ((m_Choice == NULL) ||
+ (m_ValidateByData && !m_Choice->property("data").isValid())) {
+ return QString();
+ } else {
+ return m_Choice->text();
+ }
+}
+
+
+void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button)
+{
+ m_Choice = button;
+ if (!m_ValidateByData || m_Choice->property("data").isValid()) {
+ this->accept();
+ } else {
+ this->reject();
+ }
+}
+
+void SelectionDialog::on_cancelButton_clicked()
+{
+ this->reject();
+}
diff --git a/src/selectiondialog.h b/src/selectiondialog.h
index a2844e97..fc04291e 100644
--- a/src/selectiondialog.h
+++ b/src/selectiondialog.h
@@ -17,50 +17,52 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef SELECTIONDIALOG_H
-#define SELECTIONDIALOG_H
-
-#include <QDialog>
-#include <QAbstractButton>
-
-namespace Ui {
-class SelectionDialog;
-}
-
-class SelectionDialog : public QDialog
-{
- Q_OBJECT
-
-public:
-
- explicit SelectionDialog(const QString &description, QWidget *parent = 0);
-
- ~SelectionDialog();
-
- /**
- * @brief add a choice to the dialog
- * @param buttonText the text to be displayed on the button
- * @param description the description that shows up under in small letters inside the button
- * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant)
- * all buttons that contain no data will be treated as "cancel" buttons
- */
- void addChoice(const QString &buttonText, const QString &description, const QVariant &data);
-
- QVariant getChoiceData();
- QString getChoiceString();
-
-private slots:
-
- void on_buttonBox_clicked(QAbstractButton *button);
-
- void on_cancelButton_clicked();
-
-private:
-
- Ui::SelectionDialog *ui;
- QAbstractButton *m_Choice;
- bool m_ValidateByData;
-
-};
-
-#endif // SELECTIONDIALOG_H
+#ifndef SELECTIONDIALOG_H
+#define SELECTIONDIALOG_H
+
+#include <QDialog>
+#include <QAbstractButton>
+
+namespace Ui {
+class SelectionDialog;
+}
+
+class SelectionDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ explicit SelectionDialog(const QString &description, QWidget *parent = 0);
+
+ ~SelectionDialog();
+
+ /**
+ * @brief add a choice to the dialog
+ * @param buttonText the text to be displayed on the button
+ * @param description the description that shows up under in small letters inside the button
+ * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant)
+ * all buttons that contain no data will be treated as "cancel" buttons
+ */
+ void addChoice(const QString &buttonText, const QString &description, const QVariant &data);
+
+ int numChoices() const;
+
+ QVariant getChoiceData();
+ QString getChoiceString();
+
+private slots:
+
+ void on_buttonBox_clicked(QAbstractButton *button);
+
+ void on_cancelButton_clicked();
+
+private:
+
+ Ui::SelectionDialog *ui;
+ QAbstractButton *m_Choice;
+ bool m_ValidateByData;
+
+};
+
+#endif // SELECTIONDIALOG_H
diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp
index 00bb42fd..b580a226 100644
--- a/src/shared/gameinfo.cpp
+++ b/src/shared/gameinfo.cpp
@@ -172,6 +172,14 @@ std::wstring GameInfo::getLogDir() const
}
+std::wstring GameInfo::getLootDir() const
+{
+ std::wostringstream temp;
+ temp << m_OrganizerDirectory << "\\loot";
+ return temp.str();
+}
+
+
std::wstring GameInfo::getTutorialDir() const
{
std::wostringstream temp;
diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h
index 10775e6c..89c9402d 100644
--- a/src/shared/gameinfo.h
+++ b/src/shared/gameinfo.h
@@ -113,6 +113,7 @@ public:
virtual std::wstring getCacheDir() const;
virtual std::wstring getOverwriteDir() const;
virtual std::wstring getLogDir() const;
+ virtual std::wstring getLootDir() const;
virtual std::wstring getTutorialDir() const;
virtual bool requiresBSAInvalidation() const { return true; }
diff --git a/src/spawn.cpp b/src/spawn.cpp
index b1c5e963..6adafba0 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -36,10 +36,23 @@ using namespace MOShared;
static const int BUFSIZE = 4096;
-bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, HANDLE& processHandle, HANDLE& threadHandle)
+bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended,
+ HANDLE stdOut, HANDLE stdErr,
+ HANDLE& processHandle, HANDLE& threadHandle)
{
+ BOOL inheritHandles = FALSE;
STARTUPINFO si;
::ZeroMemory(&si, sizeof(si));
+ if (stdOut != INVALID_HANDLE_VALUE) {
+ si.hStdOutput = stdOut;
+ inheritHandles = TRUE;
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ }
+ if (stdErr != INVALID_HANDLE_VALUE) {
+ si.hStdError = stdErr;
+ inheritHandles = TRUE;
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ }
si.cb = sizeof(si);
int length = wcslen(binary) + wcslen(arguments) + 4;
wchar_t *commandLine = NULL;
@@ -74,7 +87,7 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus
BOOL success = ::CreateProcess(NULL,
commandLine,
NULL, NULL, // no special process or thread attributes
- FALSE, // don't inherit handle
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL
NULL, // same environment as parent
currentDirectory, // current directory
@@ -95,14 +108,22 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus
}
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString& profileName, int logLevel, const QDir &currentDirectory, bool hooked)
+HANDLE startBinary(const QFileInfo &binary,
+ const QString &arguments,
+ const QString& profileName,
+ int logLevel,
+ const QDir &currentDirectory,
+ bool hooked,
+ HANDLE stdOut,
+ HANDLE stdErr)
{
HANDLE processHandle, threadHandle;
std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, processHandle, threadHandle)) {
+ if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked,
+ stdOut, stdErr, processHandle, threadHandle)) {
reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
return INVALID_HANDLE_VALUE;
}
diff --git a/src/spawn.h b/src/spawn.h
index 48320fea..3f037119 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -52,11 +52,17 @@ private:
* @param arguments arguments to pass to the binary
* @param profileName name of the active profile
* @param currentDirectory the directory to use as the working directory to run in
+ * @param logLevel log level to be used by the hook library. Ignored if hooked is false
* @param hooked if set, the binary is started with mo injected
+ * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process
+ * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process
* @return the process handle
* @todo is the profile name even used any more?
* @todo is the hooked parameter used?
**/
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, const QDir &currentDirectory, bool hooked);
+HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel,
+ const QDir &currentDirectory, bool hooked,
+ HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
#endif // SPAWN_H
+