mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
91
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce2450885c | ||
|
|
d78cb58593 | ||
|
|
2c54d128b4 | ||
|
|
f71288db16 | ||
|
|
8bac17c2c5 | ||
|
|
4509911a8e | ||
|
|
f098491306 | ||
|
|
bcaceb9637 | ||
|
|
b7e8e69f47 | ||
|
|
a8bcbf6555 | ||
|
|
aafd523736 | ||
|
|
4a9da205cd | ||
|
|
68e7820154 | ||
|
|
3c6190d713 | ||
|
|
1197b562f6 | ||
|
|
7df5329ea0 | ||
|
|
dc3dcd9500 | ||
|
|
cc8fb9bd23 | ||
|
|
9638a797d1 | ||
|
|
8308dfcbff | ||
|
|
dfbaed0ade | ||
|
|
e475893ff4 | ||
|
|
40a4bf1e3a | ||
|
|
3ee2598533 | ||
|
|
234ee923fe | ||
|
|
88ee41ae85 | ||
|
|
8a29bc5110 | ||
|
|
77dbd7ff31 | ||
|
|
7fab050adb | ||
|
|
b547045203 | ||
|
|
fcd2e1473f | ||
|
|
287dd6d805 | ||
|
|
717c2ca9cf | ||
|
|
abd1a11768 | ||
|
|
34636301ea | ||
|
|
f298ea0ae3 | ||
|
|
eee1b4b3f1 | ||
|
|
00120605fb | ||
|
|
32088b47f1 | ||
|
|
726b381ea7 | ||
|
|
071550f7fb | ||
|
|
f8a50edd0f | ||
|
|
08c7113ecb | ||
|
|
d436fe8cb3 | ||
|
|
d5183ebe6b | ||
|
|
c72326ee4b | ||
|
|
75e90f2f3f | ||
|
|
be012715f3 | ||
|
|
2b86a14c86 | ||
|
|
c96c01f40e | ||
|
|
d6edc5d6b7 | ||
|
|
4040dc12b7 | ||
|
|
d4fc7c3de1 | ||
|
|
27c4861907 | ||
|
|
8decb9df68 | ||
|
|
8365f26d83 | ||
|
|
0ca21d9a5e | ||
|
|
f60d0e5ec6 | ||
|
|
d22d77d921 | ||
|
|
7084021067 | ||
|
|
1dd2acf07f | ||
|
|
3fad475013 | ||
|
|
010a5d80d6 | ||
|
|
2bd755743c | ||
|
|
9eb1dd0ec0 | ||
|
|
e473caf14a | ||
|
|
1c87f1d8d4 | ||
|
|
bb6cde175a | ||
|
|
bd93864ed2 | ||
|
|
406d6f28c0 | ||
|
|
ee4c364ccc | ||
|
|
607fafa364 | ||
|
|
7dc28b10d9 | ||
|
|
9f0c8c31a6 | ||
|
|
2fe627c261 | ||
|
|
879c37984e | ||
|
|
1cf8a2bfee | ||
|
|
7531a860cf | ||
|
|
31c8415e88 | ||
|
|
48274f3357 | ||
|
|
f486e6fa4e | ||
|
|
9e5a64b545 | ||
|
|
3dd05ff869 | ||
|
|
eba98e8469 | ||
|
|
73418ec616 | ||
|
|
7283e6c3ba | ||
|
|
d179ad01c8 | ||
|
|
04d80bb567 | ||
|
|
595f6eccf6 | ||
|
|
f058125e66 | ||
|
|
7d36bc20d0 |
@@ -2,7 +2,7 @@
|
||||
name: Game support Request
|
||||
about: Request support for a new game
|
||||
title: Add support for game [GAME NAME]
|
||||
labels: 'additional games support, Feature Request, area: mo2 game plugins'
|
||||
labels: 'Feature Request, additional games support, area: mo2 game plugins'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -18,7 +18,7 @@ assignees: ''
|
||||
- **Nexus ID [optional]:** ID of the game on Nexus (you can usually find this in Nexus URL).
|
||||
- **Executable:** Name of the main executable for the game (relative to the game folder).
|
||||
- **Launcher [optional]:** Name of the game launcher (relative to the game folder).
|
||||
- **Data path:** Path to the data folder of the game (relative to the game folder).
|
||||
- **Data path:** Path to the data folder of the game (relative to the game folder). Please note that the Virtual Files System often does not work for top level dlls or exe files.
|
||||
- **Documents path:** Path to folder containing INI files, etc., for the game (usually under "My Games", or the game folder itself).
|
||||
- **Saves directory [optional]:** Path to the folder containing save games (this default to the path above).
|
||||
- **Save extension [optional]:** Extension of the saves
|
||||
|
||||
+3
-3
@@ -52,10 +52,10 @@ build_script:
|
||||
if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
|
||||
test: off
|
||||
artifacts:
|
||||
- path: '\modorganizer-build\install\bin'
|
||||
- path: '\modorganizer-build\install\bin\ModOrganizer.exe'
|
||||
name: Mod.Organizer-$(APPVEYOR_BUILD_VERSION)
|
||||
- path: '\modorganizer-build\install\pdb'
|
||||
name: PDBs-Mod.Organizer-$(APPVEYOR_BUILD_VERSION)
|
||||
- path: '\modorganizer-build\install\pdb\ModOrganizer.pdb'
|
||||
name: PDB-Mod.Organizer-$(APPVEYOR_BUILD_VERSION)
|
||||
deploy: off
|
||||
on_success:
|
||||
- ps: >-
|
||||
|
||||
@@ -18,6 +18,7 @@ add_filter(NAME src/application GROUPS
|
||||
multiprocess
|
||||
sanitychecks
|
||||
selfupdater
|
||||
updatedialog
|
||||
)
|
||||
|
||||
add_filter(NAME src/browser GROUPS
|
||||
|
||||
@@ -469,6 +469,11 @@
|
||||
<string notr="true">Drew Warwick</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">foresto</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">GamerPoet</string>
|
||||
|
||||
+13
-6
@@ -158,14 +158,14 @@ protected:
|
||||
currentName = std::get<0>(p)[0];
|
||||
}
|
||||
|
||||
// If the name is different, we need to create a directory from what we have
|
||||
// If the name is different, we need to create a directory from what we have
|
||||
// accumulated:
|
||||
if (currentName != std::get<0>(p)[0]) {
|
||||
|
||||
// We may or may not have an index here, it depends on the type of archive (some archives list
|
||||
// intermediate non-empty folders, some don't):
|
||||
entries.push_back(std::make_shared<ArchiveFileTreeImpl>(parent, currentName, currentIndex, std::move(currentFiles)));
|
||||
|
||||
|
||||
currentFiles.clear(); // Back to a valid state.
|
||||
|
||||
// Reset the index:
|
||||
@@ -200,7 +200,7 @@ protected:
|
||||
if (currentName != "") {
|
||||
entries.push_back(std::make_shared<ArchiveFileTreeImpl>(parent, currentName, currentIndex, std::move(currentFiles)));
|
||||
}
|
||||
|
||||
|
||||
// Let the parent class sort the entries:
|
||||
return false;
|
||||
}
|
||||
@@ -214,7 +214,7 @@ private:
|
||||
mutable std::vector<File> m_Files;
|
||||
};
|
||||
|
||||
std::shared_ptr<ArchiveFileTree> ArchiveFileTree::makeTree(Archive const& archive)
|
||||
std::shared_ptr<ArchiveFileTree> ArchiveFileTree::makeTree(Archive const& archive)
|
||||
{
|
||||
auto const& data = archive.getFileList();
|
||||
|
||||
@@ -222,9 +222,16 @@ std::shared_ptr<ArchiveFileTree> ArchiveFileTree::makeTree(Archive const& archiv
|
||||
files.reserve(data.size());
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
// Ignore "." and ".." as they're useless and muck things up
|
||||
if (data[i]->getArchiveFilePath().compare(L".") == 0 ||
|
||||
data[i]->getArchiveFilePath().compare(L"..") == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
files.push_back(std::make_tuple(
|
||||
QString::fromStdWString(data[i]->getArchiveFilePath()).replace("\\", "/").split("/", Qt::SkipEmptyParts),
|
||||
data[i]->isDirectory(),
|
||||
QString::fromStdWString(data[i]->getArchiveFilePath()).replace("\\", "/").split("/", Qt::SkipEmptyParts),
|
||||
data[i]->isDirectory(),
|
||||
(int) i));
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -251,7 +251,7 @@ std::optional<int> CommandLine::runPostApplication(MOApplication& a)
|
||||
env::Console c;
|
||||
|
||||
if (auto i=InstanceManager::singleton().currentInstance()) {
|
||||
std::cout << i->name().toStdString() << "\n";
|
||||
std::cout << i->displayName().toStdString() << "\n";
|
||||
} else {
|
||||
std::cout << "no instance configured\n";
|
||||
}
|
||||
@@ -289,9 +289,8 @@ std::optional<int> CommandLine::runPostOrganizer(OrganizerCore& core)
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
reportError(
|
||||
QObject::tr("failed to start shortcut: %1").arg(e.what()));
|
||||
catch (std::exception&) {
|
||||
// user was already warned
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -451,7 +450,7 @@ std::optional<QString> CommandLine::instance() const
|
||||
// note that moshortcut:// overrides -i
|
||||
|
||||
if (m_shortcut.isValid() && m_shortcut.hasInstance()) {
|
||||
return m_shortcut.instance();
|
||||
return m_shortcut.instanceName();
|
||||
} else if (m_vm.count("instance")) {
|
||||
return QString::fromStdString(m_vm["instance"].as<std::string>());
|
||||
}
|
||||
@@ -817,7 +816,7 @@ std::optional<int> RunCommand::runPostOrganizer(OrganizerCore& core)
|
||||
reportError(
|
||||
QObject::tr("Executable '%1' not found in instance '%2'.")
|
||||
.arg(program)
|
||||
.arg(InstanceManager::singleton().currentInstance()->name()));
|
||||
.arg(InstanceManager::singleton().currentInstance()->displayName()));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <iplugingame.h>
|
||||
#include <report.h>
|
||||
#include <utility.h>
|
||||
#include "filesystemutilities.h"
|
||||
|
||||
namespace cid
|
||||
{
|
||||
@@ -292,7 +293,7 @@ void GamePage::select(IPluginGame* game, const QString& dir)
|
||||
Game* checked = findGame(game);
|
||||
|
||||
if (checked) {
|
||||
if (!checked->installed) {
|
||||
if (!checked->installed || (detectMicrosoftStore(checked->dir) && !confirmMicrosoftStore(checked->dir, checked->game))) {
|
||||
if (dir.isEmpty()) {
|
||||
// the selected game has no installation directory and none was given,
|
||||
// ask the user
|
||||
@@ -304,6 +305,9 @@ void GamePage::select(IPluginGame* game, const QString& dir)
|
||||
if (path.isEmpty()) {
|
||||
// cancelled
|
||||
checked = nullptr;
|
||||
} else if (detectMicrosoftStore(path) && !confirmMicrosoftStore(path, game)) {
|
||||
// cancelled
|
||||
checked = nullptr;
|
||||
} else {
|
||||
// check whether a plugin supports the given directory; this can
|
||||
// return the same plugin, a different one, or null
|
||||
@@ -353,6 +357,13 @@ void GamePage::selectCustom()
|
||||
return;
|
||||
}
|
||||
|
||||
// Microsoft store games are not supported
|
||||
if (detectMicrosoftStore(path) && !confirmMicrosoftStore(path, nullptr)) {
|
||||
// reselect the previous button
|
||||
selectButton(m_selection);
|
||||
return;
|
||||
}
|
||||
|
||||
// try to find a plugin that likes this directory
|
||||
for (auto& g : m_games) {
|
||||
if (g->game->looksValid(path)) {
|
||||
@@ -593,6 +604,11 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g)
|
||||
return g;
|
||||
}
|
||||
|
||||
if (detectMicrosoftStore(path) && confirmMicrosoftStore(path, g->game)) {
|
||||
// okay
|
||||
return g;
|
||||
}
|
||||
|
||||
// the selected game can't use that folder, find another one
|
||||
IPluginGame* otherGame = nullptr;
|
||||
|
||||
@@ -639,6 +655,35 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g)
|
||||
return g;
|
||||
}
|
||||
|
||||
bool GamePage::detectMicrosoftStore(const QString& path)
|
||||
{
|
||||
return path.contains("/ModifiableWindowsApps/") ||
|
||||
path.contains("/WindowsApps/");
|
||||
}
|
||||
|
||||
bool GamePage::confirmMicrosoftStore(const QString& path, IPluginGame* game)
|
||||
{
|
||||
const auto r = TaskDialog(&m_dlg)
|
||||
.title(QObject::tr("Microsoft Store game"))
|
||||
.main(QObject::tr("Microsoft Store game"))
|
||||
.content(QObject::tr(
|
||||
"The folder %1 seems to be a Microsoft Store game install. Games"
|
||||
" installed through the Microsoft Store are not supported by Mod Organizer"
|
||||
" and will not work properly.")
|
||||
.arg(path))
|
||||
.button({
|
||||
game ? QObject::tr("Use this folder for %1").arg(game->gameName())
|
||||
: QObject::tr("Use this folder"),
|
||||
QObject::tr("I know what I'm doing"),
|
||||
QMessageBox::Ignore})
|
||||
.button({
|
||||
QObject::tr("Cancel"),
|
||||
QMessageBox::Cancel})
|
||||
.exec();
|
||||
|
||||
return (r == QMessageBox::Ignore);
|
||||
}
|
||||
|
||||
bool GamePage::confirmUnknown(const QString& path, IPluginGame* game)
|
||||
{
|
||||
const auto r = TaskDialog(&m_dlg)
|
||||
@@ -856,7 +901,7 @@ QString NamePage::selectedInstanceName() const
|
||||
}
|
||||
|
||||
const auto text = ui->instanceName->text().trimmed();
|
||||
return InstanceManager::singleton().sanitizeInstanceName(text);
|
||||
return MOBase::sanitizeFileName(text);
|
||||
}
|
||||
|
||||
void NamePage::onChanged()
|
||||
@@ -883,7 +928,7 @@ bool NamePage::checkName(QString parentDir, QString name)
|
||||
if (name.isEmpty()) {
|
||||
empty = true;
|
||||
} else {
|
||||
if (InstanceManager::singleton().validInstanceName(name)) {
|
||||
if (MOBase::validFileName(name)) {
|
||||
exists = QDir(parentDir).exists(name);
|
||||
} else {
|
||||
invalid = true;
|
||||
@@ -1108,7 +1153,7 @@ bool PathsPage::checkPath(
|
||||
} else {
|
||||
const QDir d(path);
|
||||
|
||||
if (m.validInstanceName(d.dirName())) {
|
||||
if (MOBase::validFileName(d.dirName())) {
|
||||
if (m_dlg.rawCreationInfo().type == CreateInstanceDialog::Portable) {
|
||||
// the default data path for a portable instance is the application
|
||||
// directory, so it's not an error if it exists
|
||||
|
||||
@@ -354,6 +354,15 @@ private:
|
||||
MOBase::IPluginGame* confirmOtherGame(
|
||||
const QString& path,
|
||||
MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame);
|
||||
|
||||
// detects if the given path likely contains a Microsoft Store game
|
||||
//
|
||||
bool detectMicrosoftStore(const QString& path);
|
||||
|
||||
// tells the user that the path probably contains a Microsoft Store game that
|
||||
// is not supported, returns true if the user decides to accept anyway.
|
||||
//
|
||||
bool confirmMicrosoftStore(const QString& path, MOBase::IPluginGame* game);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
|
||||
if ((leftIndex < m_manager.numTotalDownloads())
|
||||
&& (rightIndex < m_manager.numTotalDownloads())) {
|
||||
if (left.column() == DownloadList::COL_NAME) {
|
||||
return m_manager.getFileName(left.row()).compare(m_manager.getFileName(right.row()), Qt::CaseInsensitive) < 0;
|
||||
return left.data(Qt::DisplayRole).toString().compare(right.data(Qt::DisplayRole).toString(), Qt::CaseInsensitive) < 0;
|
||||
} else if (left.column() == DownloadList::COL_MODNAME) {
|
||||
QString leftName, rightName;
|
||||
|
||||
@@ -275,7 +275,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
|
||||
if (leftState == rightState)
|
||||
return m_manager.getFileTime(left.row()) < m_manager.getFileTime(right.row());
|
||||
else
|
||||
return leftState > rightState;
|
||||
return leftState < rightState;
|
||||
} else if (left.column() == DownloadList::COL_SIZE) {
|
||||
return m_manager.getFileSize(left.row()) < m_manager.getFileSize(right.row());
|
||||
} else if (left.column() == DownloadList::COL_FILETIME) {
|
||||
|
||||
@@ -33,6 +33,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "shared/util.h"
|
||||
#include <utility.h>
|
||||
#include <report.h>
|
||||
#include "filesystemutilities.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QFileInfo>
|
||||
@@ -1456,7 +1457,7 @@ void DownloadManager::markUninstalled(QString fileName)
|
||||
|
||||
QString DownloadManager::getDownloadFileName(const QString &baseName, bool rename) const
|
||||
{
|
||||
QString fullPath = m_OutputDirectory + "/" + baseName;
|
||||
QString fullPath = m_OutputDirectory + "/" + MOBase::sanitizeFileName(baseName);
|
||||
if (QFile::exists(fullPath) && rename) {
|
||||
int i = 1;
|
||||
while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) {
|
||||
@@ -1476,7 +1477,7 @@ QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply)
|
||||
|
||||
std::cmatch result;
|
||||
if (std::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) {
|
||||
return QString::fromUtf8(result.str(1).c_str());
|
||||
return MOBase::sanitizeFileName(QString::fromUtf8(result.str(1).c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -418,9 +418,9 @@ QString prependToPath(const QString& s)
|
||||
return old;
|
||||
}
|
||||
|
||||
QString setPath(const QString& s)
|
||||
void setPath(const QString& s)
|
||||
{
|
||||
return set("PATH", s);
|
||||
set("PATH", s);
|
||||
}
|
||||
|
||||
QString get(const QString& name)
|
||||
@@ -457,11 +457,9 @@ QString get(const QString& name)
|
||||
return QString::fromWCharArray(buffer.get(), realSize);
|
||||
}
|
||||
|
||||
QString set(const QString& n, const QString& v)
|
||||
void set(const QString& n, const QString& v)
|
||||
{
|
||||
auto old = get(n);
|
||||
::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str());
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -233,12 +233,12 @@ private:
|
||||
// environment variables
|
||||
//
|
||||
QString get(const QString& name);
|
||||
QString set(const QString& name, const QString& value);
|
||||
void set(const QString& name, const QString& value);
|
||||
|
||||
QString path();
|
||||
QString appendToPath(const QString& s);
|
||||
QString prependToPath(const QString& s);
|
||||
QString setPath(const QString& s);
|
||||
void setPath(const QString& s);
|
||||
|
||||
|
||||
class Service
|
||||
|
||||
+23
-5
@@ -232,8 +232,8 @@ void forEachEntryImpl(
|
||||
|
||||
if (status < 0) {
|
||||
log::error(
|
||||
"NtOpenFile() failed for '{}', {}",
|
||||
toString(poa), formatSystemMessage(status));
|
||||
"failed to open directory '{}': {}",
|
||||
toString(poa), formatNtMessage(status));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -264,8 +264,9 @@ void forEachEntryImpl(
|
||||
break;
|
||||
} else if (status < 0) {
|
||||
log::error(
|
||||
"NtQueryDirectoryFile() failed for '{}', {}",
|
||||
toString(poa), formatSystemMessage(status));
|
||||
"failed to read directory '{}': {}",
|
||||
toString(poa), formatNtMessage(status));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -321,6 +322,23 @@ void forEachEntryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring makeNtPath(const std::wstring& path)
|
||||
{
|
||||
constexpr const wchar_t* nt_prefix = L"\\??\\";
|
||||
constexpr const wchar_t* nt_unc_prefix = L"\\??\\UNC\\";
|
||||
constexpr const wchar_t* share_prefix = L"\\\\";
|
||||
|
||||
if (path.starts_with(nt_prefix)) {
|
||||
// already an nt path
|
||||
return path;
|
||||
} else if (path.starts_with(share_prefix)) {
|
||||
// network shared need \??\UNC\ as a prefix
|
||||
return nt_unc_prefix + path.substr(2);
|
||||
} else {
|
||||
// prepend the \??\ prefix
|
||||
return nt_prefix + path;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryWalker::forEachEntry(
|
||||
const std::wstring& path, void* cx,
|
||||
@@ -335,7 +353,7 @@ void DirectoryWalker::forEachEntry(
|
||||
NtClose = (NtClose_type)::GetProcAddress(m.get(), "NtClose");
|
||||
}
|
||||
|
||||
const std::wstring ntpath = std::wstring(L"\\??\\") + path;
|
||||
const std::wstring ntpath = makeNtPath(path);
|
||||
|
||||
UNICODE_STRING ObjectName = {};
|
||||
ObjectName.Buffer = const_cast<wchar_t*>(ntpath.c_str());
|
||||
|
||||
+23
-7
@@ -130,7 +130,7 @@ Module::FileInfo Module::getFileInfo() const
|
||||
return {};
|
||||
}
|
||||
|
||||
log::error(
|
||||
log::debug(
|
||||
"GetFileVersionInfoSizeW() failed on '{}', {}",
|
||||
m_path, formatSystemMessage(e));
|
||||
|
||||
@@ -268,7 +268,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const
|
||||
if (h.get() == INVALID_HANDLE_VALUE) {
|
||||
const auto e = GetLastError();
|
||||
|
||||
log::error(
|
||||
log::debug(
|
||||
"can't open file '{}' for timestamp, {}",
|
||||
m_path, formatSystemMessage(e));
|
||||
|
||||
@@ -603,9 +603,16 @@ Process getProcessTreeFromProcess(HANDLE h)
|
||||
|
||||
std::vector<DWORD> processesInJob(HANDLE h)
|
||||
{
|
||||
for (int tries=0; tries<5; ++tries) {
|
||||
DWORD maxIds = 100;
|
||||
const int MaxTries = 5;
|
||||
|
||||
// doubled MaxTries times on failure
|
||||
DWORD maxIds = 100;
|
||||
|
||||
// for logging
|
||||
DWORD lastCount=0, lastAssigned=0;
|
||||
|
||||
|
||||
for (int tries=0; tries<MaxTries; ++tries) {
|
||||
const DWORD idsSize = sizeof(ULONG_PTR) * maxIds;
|
||||
const DWORD bufferSize = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + idsSize;
|
||||
|
||||
@@ -617,8 +624,10 @@ std::vector<DWORD> processesInJob(HANDLE h)
|
||||
|
||||
if (!r) {
|
||||
const auto e = GetLastError();
|
||||
log::error("failed to get process ids in job, {}", formatSystemMessage(e));
|
||||
return {};
|
||||
if (e != ERROR_MORE_DATA) {
|
||||
log::error("failed to get process ids in job, {}", formatSystemMessage(e));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (ids->NumberOfProcessIdsInList >= ids->NumberOfAssignedProcesses) {
|
||||
@@ -632,9 +641,16 @@ std::vector<DWORD> processesInJob(HANDLE h)
|
||||
|
||||
// try again with a larger buffer
|
||||
maxIds *= 2;
|
||||
|
||||
// for logging
|
||||
lastCount = ids->NumberOfProcessIdsInList;
|
||||
lastAssigned = ids->NumberOfAssignedProcesses;
|
||||
}
|
||||
|
||||
log::error("failed to get processes in job, can't get a buffer large enough");
|
||||
log::error(
|
||||
"failed to get processes in job, can't get a buffer large enough, "
|
||||
"{}/{} ids", lastCount, lastAssigned);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -145,11 +145,13 @@ Shortcut::Shortcut()
|
||||
Shortcut::Shortcut(const Executable& exe)
|
||||
: Shortcut()
|
||||
{
|
||||
const auto i = *InstanceManager::singleton().currentInstance();
|
||||
|
||||
m_name = exe.title();
|
||||
m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
|
||||
|
||||
m_arguments = QString("\"moshortcut://%1:%2\"")
|
||||
.arg(InstanceManager::singleton().currentInstance()->name())
|
||||
.arg(i.isPortable() ? "" : i.displayName())
|
||||
.arg(exe.title());
|
||||
|
||||
m_description = QString("Run %1 with ModOrganizer").arg(exe.title());
|
||||
|
||||
@@ -21,7 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "installationmanager.h"
|
||||
|
||||
#include "utility.h"
|
||||
#include "filesystemutilities.h"
|
||||
#include "report.h"
|
||||
#include "categories.h"
|
||||
#include "questionboxmemory.h"
|
||||
|
||||
+10
-38
@@ -32,6 +32,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <iplugingame.h>
|
||||
#include <utility.h>
|
||||
#include <log.h>
|
||||
#include "filesystemutilities.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
@@ -48,7 +49,7 @@ Instance::Instance(QString dir, bool portable, QString profileName) :
|
||||
{
|
||||
}
|
||||
|
||||
QString Instance::name() const
|
||||
QString Instance::displayName() const
|
||||
{
|
||||
if (isPortable())
|
||||
return QObject::tr("Portable");
|
||||
@@ -105,7 +106,7 @@ bool Instance::isActive() const
|
||||
if (m_portable) {
|
||||
return i->isPortable();
|
||||
} else {
|
||||
return (i->name() == name());
|
||||
return (i->displayName() == displayName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,7 +722,7 @@ const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(
|
||||
|
||||
QString InstanceManager::makeUniqueName(const QString& instanceName) const
|
||||
{
|
||||
const QString sanitized = sanitizeInstanceName(instanceName);
|
||||
const QString sanitized = MOBase::sanitizeFileName(instanceName);
|
||||
|
||||
// trying "name (N)"
|
||||
QString name = sanitized;
|
||||
@@ -742,35 +743,6 @@ bool InstanceManager::instanceExists(const QString& instanceName) const
|
||||
return root.exists(instanceName);
|
||||
}
|
||||
|
||||
QString InstanceManager::sanitizeInstanceName(const QString &name) const
|
||||
{
|
||||
QString new_name = name;
|
||||
|
||||
// Restrict the allowed characters
|
||||
new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]"));
|
||||
|
||||
// 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);
|
||||
}
|
||||
return new_name;
|
||||
}
|
||||
|
||||
bool InstanceManager::validInstanceName(const QString& instanceName) const
|
||||
{
|
||||
if (instanceName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (instanceName == sanitizeInstanceName(instanceName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::unique_ptr<Instance> selectInstance()
|
||||
{
|
||||
auto& m = InstanceManager::singleton();
|
||||
@@ -822,7 +794,7 @@ SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc)
|
||||
CreateInstanceDialog dlg(pc, nullptr);
|
||||
|
||||
// only show the game page
|
||||
dlg.setSinglePage<cid::GamePage>(instance.name());
|
||||
dlg.setSinglePage<cid::GamePage>(instance.displayName());
|
||||
|
||||
dlg.show();
|
||||
dlg.activateWindow();
|
||||
@@ -860,7 +832,7 @@ SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc)
|
||||
instance.gamePlugin(), instance.gameDirectory());
|
||||
|
||||
// only show the variant page
|
||||
dlg.setSinglePage<cid::VariantsPage>(instance.name());
|
||||
dlg.setSinglePage<cid::VariantsPage>(instance.displayName());
|
||||
|
||||
dlg.show();
|
||||
dlg.activateWindow();
|
||||
@@ -897,7 +869,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
|
||||
|
||||
reportError(
|
||||
QObject::tr("Cannot open instance '%1', failed to read INI file %2.")
|
||||
.arg(instance.name()).arg(instance.iniPath()));
|
||||
.arg(instance.displayName()).arg(instance.iniPath()));
|
||||
|
||||
return SetupInstanceResults::SelectAnother;
|
||||
}
|
||||
@@ -914,7 +886,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
|
||||
QObject::tr(
|
||||
"Cannot open instance '%1', the managed game was not found in the INI "
|
||||
"file %2. Select the game managed by this instance.")
|
||||
.arg(instance.name()).arg(instance.iniPath()));
|
||||
.arg(instance.displayName()).arg(instance.iniPath()));
|
||||
|
||||
return selectGame(instance, pc);
|
||||
}
|
||||
@@ -928,7 +900,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
|
||||
QObject::tr(
|
||||
"Cannot open instance '%1', the game plugin '%2' doesn't exist. It "
|
||||
"may have been deleted by an antivirus. Select another instance.")
|
||||
.arg(instance.name()).arg(instance.gameName()));
|
||||
.arg(instance.displayName()).arg(instance.gameName()));
|
||||
|
||||
return SetupInstanceResults::SelectAnother;
|
||||
}
|
||||
@@ -943,7 +915,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
|
||||
"Cannot open instance '%1', the game directory '%2' doesn't exist or "
|
||||
"the game plugin '%3' doesn't recognize it. Select the game managed "
|
||||
"by this instance.")
|
||||
.arg(instance.name())
|
||||
.arg(instance.displayName())
|
||||
.arg(instance.gameDirectory())
|
||||
.arg(instance.gameName()));
|
||||
|
||||
|
||||
+6
-11
@@ -132,9 +132,14 @@ public:
|
||||
// returns the instance name; this is the directory name or "Portable" for
|
||||
// portable instances
|
||||
//
|
||||
// be careful when using this function to check whether two instances are the
|
||||
// same, some parts of MO use an empty string to represent portable instances,
|
||||
// but this function will return "Portable" for them; it's safer to check
|
||||
// for isPortable() first
|
||||
//
|
||||
// can be called without setup()
|
||||
//
|
||||
QString name() const;
|
||||
QString displayName() const;
|
||||
|
||||
// returns either:
|
||||
// 1) the game name from the INI, if readFromIni() was called;
|
||||
@@ -300,10 +305,6 @@ public:
|
||||
//
|
||||
std::vector<QString> globalInstancePaths() const;
|
||||
|
||||
// returns `name` modified so that it is a valid instance name
|
||||
//
|
||||
QString sanitizeInstanceName(const QString &name) const;
|
||||
|
||||
// sanitizes the given instance name and either
|
||||
// 1) returns it if there is no instance with this name
|
||||
// 2) tries to add " (N)" at the end until it works
|
||||
@@ -316,12 +317,6 @@ public:
|
||||
//
|
||||
bool instanceExists(const QString& instanceName) const;
|
||||
|
||||
// returns whether the given instance name would be a valid name; this does
|
||||
// not check whether the instance already exists, it's basiscally just a check
|
||||
// against what sanitizeInstanceName() returns
|
||||
//
|
||||
bool validInstanceName(const QString& instanceName) const;
|
||||
|
||||
// returns the absolute path of a global instance with the given name; this
|
||||
// does not check if the name is valid or if exists
|
||||
//
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <utility.h>
|
||||
#include <report.h>
|
||||
#include <iplugingame.h>
|
||||
#include "filesystemutilities.h"
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
@@ -109,10 +110,10 @@ QString getInstanceName(
|
||||
|
||||
if (text->text().isEmpty()) {
|
||||
error->setText("");
|
||||
} else if (!m.validInstanceName(text->text())) {
|
||||
} else if (!MOBase::validFileName(text->text())) {
|
||||
error->setText(QObject::tr("The instance name must be a valid folder name."));
|
||||
} else {
|
||||
const auto name = m.sanitizeInstanceName(text->text());
|
||||
const auto name = MOBase::sanitizeFileName(text->text());
|
||||
|
||||
if ((name != oldName) && m.instanceExists(text->text())) {
|
||||
error->setText(QObject::tr("An instance with this name already exists."));
|
||||
@@ -136,7 +137,7 @@ QString getInstanceName(
|
||||
return {};
|
||||
}
|
||||
|
||||
return m.sanitizeInstanceName(text->text());
|
||||
return MOBase::sanitizeFileName(text->text());
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +222,7 @@ void InstanceManagerDialog::updateInstances()
|
||||
|
||||
// sort first, prepend portable after so it's always on top
|
||||
std::sort(m_instances.begin(), m_instances.end(), [](auto&& a, auto&& b) {
|
||||
return (MOBase::naturalCompare(a->name(), b->name()) < 0);
|
||||
return (MOBase::naturalCompare(a->displayName(), b->displayName()) < 0);
|
||||
});
|
||||
|
||||
if (m.portableInstanceExists()) {
|
||||
@@ -249,7 +250,7 @@ void InstanceManagerDialog::updateList()
|
||||
for (std::size_t i=0; i<m_instances.size(); ++i) {
|
||||
const auto& ii = *m_instances[i];
|
||||
|
||||
auto* item = new QStandardItem(ii.name());
|
||||
auto* item = new QStandardItem(ii.displayName());
|
||||
item->setIcon(instanceIcon(m_pc, ii));
|
||||
|
||||
m_model->appendRow(item);
|
||||
@@ -295,7 +296,7 @@ void InstanceManagerDialog::select(std::size_t i)
|
||||
void InstanceManagerDialog::select(const QString& name)
|
||||
{
|
||||
for (std::size_t i=0; i<m_instances.size(); ++i) {
|
||||
if (m_instances[i]->name() == name) {
|
||||
if (m_instances[i]->displayName() == name) {
|
||||
select(i);
|
||||
return;
|
||||
}
|
||||
@@ -310,7 +311,7 @@ void InstanceManagerDialog::selectActiveInstance()
|
||||
|
||||
if (active) {
|
||||
for (std::size_t i=0; i<m_instances.size(); ++i) {
|
||||
if (m_instances[i]->name() == active->name()) {
|
||||
if (m_instances[i]->displayName() == active->displayName()) {
|
||||
select(i);
|
||||
|
||||
ui->list->scrollTo(
|
||||
@@ -340,7 +341,7 @@ void InstanceManagerDialog::openSelectedInstance()
|
||||
if (to.isPortable()) {
|
||||
InstanceManager::singleton().setCurrentInstance("");
|
||||
} else {
|
||||
InstanceManager::singleton().setCurrentInstance(to.name());
|
||||
InstanceManager::singleton().setCurrentInstance(to.displayName());
|
||||
}
|
||||
|
||||
if (m_restartOnSelect) {
|
||||
@@ -372,7 +373,7 @@ bool InstanceManagerDialog::confirmSwitch(const Instance& to)
|
||||
const auto r = dlg
|
||||
.title(tr("Switching instances"))
|
||||
.main(tr("Mod Organizer must restart to manage the instance '%1'.")
|
||||
.arg(to.name()))
|
||||
.arg(to.displayName()))
|
||||
.content(tr("This confirmation can be disabled in the settings."))
|
||||
.icon(QMessageBox::Question)
|
||||
.button({tr("Restart Mod Organizer"), QMessageBox::Ok})
|
||||
@@ -401,7 +402,7 @@ void InstanceManagerDialog::rename()
|
||||
|
||||
// getting new name
|
||||
const auto newName = getInstanceName(
|
||||
this, tr("Rename instance"), "", tr("Instance name"), i->name());
|
||||
this, tr("Rename instance"), "", tr("Instance name"), i->displayName());
|
||||
|
||||
if (newName.isEmpty()) {
|
||||
return;
|
||||
@@ -661,7 +662,7 @@ const Instance* InstanceManagerDialog::singleSelection() const
|
||||
|
||||
void InstanceManagerDialog::fillData(const Instance& ii)
|
||||
{
|
||||
ui->name->setText(ii.name());
|
||||
ui->name->setText(ii.displayName());
|
||||
ui->location->setText(ii.directory());
|
||||
ui->baseDirectory->setText(ii.baseDirectory());
|
||||
ui->gameName->setText(ii.gameName());
|
||||
|
||||
+7
-2
@@ -80,7 +80,8 @@ int run(int argc, char *argv[])
|
||||
// stuff that's done only once, even if MO restarts in the loop below
|
||||
app.firstTimeSetup(multiProcess);
|
||||
|
||||
// force the "Select instance" dialog on startup (only for first loop)
|
||||
// force the "Select instance" dialog on startup, only for first loop or when
|
||||
// the current instance cannot be used
|
||||
bool pick = cl.pick();
|
||||
|
||||
// MO runs in a loop because it can be restarted in several ways, such as
|
||||
@@ -105,13 +106,17 @@ int run(int argc, char *argv[])
|
||||
const auto r = app.setup(multiProcess, pick);
|
||||
pick = false;
|
||||
|
||||
if (r == RestartExitCode) {
|
||||
if (r == RestartExitCode || r == ReselectExitCode) {
|
||||
// resets things when MO is "restarted"
|
||||
app.resetForRestart();
|
||||
|
||||
// don't reprocess command line
|
||||
cl.clear();
|
||||
|
||||
if (r == ReselectExitCode) {
|
||||
pick = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (r != 0) {
|
||||
// something failed, quit
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user