diff options
Diffstat (limited to 'src')
38 files changed, 3799 insertions, 2390 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea27184b..7c29ff48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,7 +87,6 @@ SET(organizer_SRCS waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp - helper.cpp filedialogmemory.cpp executableslist.cpp editexecutablesdialog.cpp @@ -136,7 +135,6 @@ SET(organizer_SRCS apiuseraccount.cpp filerenamer.cpp texteditor.cpp - expanderwidget.cpp env.cpp envmetrics.cpp envmodule.cpp @@ -209,7 +207,6 @@ SET(organizer_HDRS waitingonclosedialog.h loadmechanism.h installationmanager.h - helper.h filedialogmemory.h executableslist.h editexecutablesdialog.h @@ -260,7 +257,6 @@ SET(organizer_HDRS apiuseraccount.h filerenamer.h texteditor.h - expanderwidget.h env.h envmetrics.h envmodule.h @@ -466,7 +462,6 @@ set(utilities csvbuilder shared/error_report eventfilter - helper shared/leaktrace persistentcookiejar serverinfo @@ -478,7 +473,6 @@ set(utilities ) set(widgets - expanderwidget genericicondelegate filerenamer filterwidget diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index a42dbeed..c2ff7d31 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -428,7 +428,7 @@ Right now the only case I know of where this needs to be overwritten is for the <item>
<widget class="QCheckBox" name="useApplicationIcon">
<property name="text">
- <string>Use Application's Icon for shortcuts</string>
+ <string>Use Application's Icon for desktop shortcuts</string>
</property>
</widget>
</item>
diff --git a/src/env.cpp b/src/env.cpp index 641eb4a7..78b5dc96 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -4,6 +4,7 @@ #include "envsecurity.h" #include "envshortcut.h" #include "envwindows.h" +#include "settings.h" #include <log.h> #include <utility.h> @@ -56,10 +57,7 @@ Console::~Console() Environment::Environment() - : m_windows(new WindowsInfo), m_metrics(new Metrics) { - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); } // anchor @@ -67,59 +65,380 @@ Environment::~Environment() = default; const std::vector<Module>& Environment::loadedModules() const { + if (m_modules.empty()){ + m_modules = getLoadedModules(); + } + return m_modules; } +std::vector<Process> Environment::runningProcesses() const +{ + return getRunningProcesses(); +} + const WindowsInfo& Environment::windowsInfo() const { + if (!m_windows) { + m_windows.reset(new WindowsInfo); + } + return *m_windows; } const std::vector<SecurityProduct>& Environment::securityProducts() const { + if (m_security.empty()) { + m_security = getSecurityProducts(); + } + return m_security; } const Metrics& Environment::metrics() const { + if (!m_metrics) { + m_metrics.reset(new Metrics); + } + return *m_metrics; } -void Environment::dump() const +void Environment::dump(const Settings& s) const { - log::debug("windows: {}", m_windows->toString()); + log::debug("windows: {}", windowsInfo().toString()); - if (m_windows->compatibilityMode()) { + if (windowsInfo().compatibilityMode()) { log::warn("MO seems to be running in compatibility mode"); } log::debug("security products:"); - for (const auto& sp : m_security) { + for (const auto& sp : securityProducts()) { log::debug(" . {}", sp.toString()); } log::debug("modules loaded in process:"); - for (const auto& m : m_modules) { + for (const auto& m : loadedModules()) { log::debug(" . {}", m.toString()); } log::debug("displays:"); - for (const auto& d : m_metrics->displays()) { + for (const auto& d : metrics().displays()) { log::debug(" . {}", d.toString()); } + + const auto r = metrics().desktopGeometry(); + log::debug( + "desktop geometry: ({},{})-({},{})", + r.left(), r.top(), r.right(), r.bottom()); + + dumpDisks(s); +} + +void Environment::dumpDisks(const Settings& s) const +{ + std::set<QString> rootPaths; + + auto dump = [&](auto&& path) { + const QFileInfo fi(path); + const QStorageInfo si(fi.absoluteFilePath()); + + if (rootPaths.contains(si.rootPath())) { + // already seen + return; + } + + // remember + rootPaths.insert(si.rootPath()); + + log::debug( + " . {} free={} MB{}", + si.rootPath(), + (si.bytesFree() / 1000 / 1000), + (si.isReadOnly() ? " (readonly)" : "")); + }; + + log::debug("drives:"); + + dump(QStorageInfo::root().rootPath()); + dump(s.paths().base()); + dump(s.paths().downloads()); + dump(s.paths().mods()); + dump(s.paths().cache()); + dump(s.paths().profiles()); + dump(s.paths().overwrite()); + dump(QCoreApplication::applicationDirPath()); } -struct Process +QString path() +{ + return get("PATH"); +} + +QString addPath(const QString& s) +{ + auto old = path(); + set("PATH", get("PATH") + ";" + s); + return old; +} + +QString setPath(const QString& s) +{ + return set("PATH", s); +} + +QString get(const QString& name) +{ + std::wstring s(4000, L' '); + + DWORD realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size())); + + if (realSize > s.size()) { + s.resize(realSize); + + ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size())); + } + + return QString::fromStdWString(s); +} + +QString set(const QString& n, const QString& v) +{ + auto old = get(n); + ::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str()); + return old; +} + + +Service::Service(QString name) + : Service(std::move(name), StartType::None, Status::None) +{ +} + +Service::Service(QString name, StartType st, Status s) + : m_name(std::move(name)), m_startType(st), m_status(s) +{ +} + +const QString& Service::name() const +{ + return m_name; +} + +bool Service::isValid() const +{ + return (m_startType != StartType::None) && (m_status != Status::None); +} + +Service::StartType Service::startType() const +{ + return m_startType; +} + +Service::Status Service::status() const +{ + return m_status; +} + +QString Service::toString() const +{ + return QString("service '%1', start=%2, status=%3") + .arg(m_name) + .arg(env::toString(m_startType)) + .arg(env::toString(m_status)); +} + + +QString toString(Service::StartType st) +{ + using ST = Service::StartType; + + switch (st) + { + case ST::None: + return "none"; + + case ST::Disabled: + return "disabled"; + + case ST::Enabled: + return "enabled"; + + default: + return QString("unknown %1").arg(static_cast<int>(st)); + } +} + +QString toString(Service::Status st) +{ + using S = Service::Status; + + switch (st) + { + case S::None: + return "none"; + + case S::Stopped: + return "stopped"; + + case S::Running: + return "running"; + + default: + return QString("unknown %1").arg(static_cast<int>(st)); + } +} + +Service::StartType getServiceStartType(SC_HANDLE s, const QString& name) +{ + DWORD needed = 0; + + if (!QueryServiceConfig(s, NULL, 0, &needed)) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + log::error( + "QueryServiceConfig() for size for '{}' failed, {}", + name, GetLastError()); + + return Service::StartType::None; + } + } + + const auto size = needed; + MallocPtr<QUERY_SERVICE_CONFIG> config( + static_cast<QUERY_SERVICE_CONFIG*>(std::malloc(size))); + + if (!QueryServiceConfig(s, config.get(), size, &needed)) { + const auto e = GetLastError(); + + log::error( + "QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e)); + + return Service::StartType::None; + } + + + switch (config->dwStartType) + { + case SERVICE_AUTO_START: // fall-through + case SERVICE_BOOT_START: + case SERVICE_DEMAND_START: + case SERVICE_SYSTEM_START: + { + return Service::StartType::Enabled; + } + + case SERVICE_DISABLED: + { + return Service::StartType::Disabled; + } + + default: + { + log::error( + "unknown service start type {} for '{}'", + config->dwStartType, name); + + return Service::StartType::None; + } + } +} + +Service::Status getServiceStatus(SC_HANDLE s, const QString& name) { - std::wstring filename; - DWORD pid; + DWORD needed = 0; + + if (!QueryServiceStatusEx(s, SC_STATUS_PROCESS_INFO, NULL, 0, &needed)) { + const auto e = GetLastError(); - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) + if (e != ERROR_INSUFFICIENT_BUFFER) { + log::error( + "QueryServiceStatusEx() for size for '{}' failed, {}", + name, GetLastError()); + + return Service::Status::None; + } + } + + const auto size = needed; + MallocPtr<SERVICE_STATUS_PROCESS> status( + static_cast<SERVICE_STATUS_PROCESS*>(std::malloc(size))); + + const auto r = QueryServiceStatusEx( + s, SC_STATUS_PROCESS_INFO, reinterpret_cast<BYTE*>(status.get()), + size, &needed); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "QueryServiceStatusEx() failed for '{}', {}", + name, formatSystemMessage(e)); + + return Service::Status::None; + } + + + switch (status->dwCurrentState) { + case SERVICE_START_PENDING: // fall-through + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + { + return Service::Status::Running; + } + + case SERVICE_STOPPED: // fall-through + case SERVICE_STOP_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_PAUSED: + { + return Service::Status::Stopped; + } + + default: + { + log::error( + "unknown service status {} for '{}'", + status->dwCurrentState, name); + + return Service::Status::None; + } } -}; +} + +Service getService(const QString& name) +{ + // service manager + const LocalPtr<SC_HANDLE> scm(OpenSCManager( + NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + + if (!scm) { + const auto e = GetLastError(); + log::error("OpenSCManager() failed, {}", formatSystemMessage(e)); + return Service(name); + } + + // service + const LocalPtr<SC_HANDLE> s(OpenService( + scm.get(), name.toStdWString().c_str(), + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + + if (!s) { + const auto e = GetLastError(); + log::error("OpenService() failed for '{}', {}", name, formatSystemMessage(e)); + return Service(name); + } + + const auto startType = getServiceStartType(s.get(), name); + const auto status = getServiceStatus(s.get(), name); + + return {name, startType, status}; +} // returns the filename of the given process or the current one @@ -177,84 +496,6 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) return {}; } -std::vector<DWORD> runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries<MaxTries; ++tries) { - auto ids = std::make_unique<DWORD[]>(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector<DWORD>(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector<Process> runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector<Process> v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - DWORD findOtherPid() { const std::wstring defaultName = L"ModOrganizer.exe"; @@ -266,7 +507,7 @@ DWORD findOtherPid() std::wclog << L"this process id is " << thisPid << L"\n"; // getting the filename for this process, assumes the other process has the - // smae one + // same one auto filename = processFilename(); if (filename.empty()) { std::wcerr @@ -279,15 +520,15 @@ DWORD findOtherPid() } // getting all running processes - const auto processes = runningProcesses(); + const auto processes = getRunningProcesses(); std::wclog << L"there are " << processes.size() << L" processes running\n"; // going through processes, trying to find one with the same name and a // different pid than this process has for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; + if (p.name() == filename) { + if (p.pid() != thisPid) { + return p.pid(); } } } @@ -1,7 +1,13 @@ +#ifndef ENV_ENV_H +#define ENV_ENV_H + +class Settings; + namespace env { class Module; +class Process; class SecurityProduct; class WindowsInfo; class Metrics; @@ -74,6 +80,37 @@ template <class T> using COMPtr = std::unique_ptr<T, COMReleaser>; +// used by MallocPtr, calls std::free() as the deleter +// +struct MallocFreer +{ + void operator()(void* p) + { + std::free(p); + } +}; + +template <class T> +using MallocPtr = std::unique_ptr<T, MallocFreer>; + + +// used by LocalPtr, calls LocalFree() as the deleter +// +template <class T> +struct LocalFreer +{ + using pointer = T; + + void operator()(T p) + { + ::LocalFree(p); + } +}; + +template <class T> +using LocalPtr = std::unique_ptr<T, LocalFreer<T>>; + + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -111,6 +148,10 @@ public: // const std::vector<Module>& loadedModules() const; + // list of running processes; not cached + // + std::vector<Process> runningProcesses() const; + // information about the operating system // const WindowsInfo& windowsInfo() const; @@ -125,16 +166,70 @@ public: // logs the environment // - void dump() const; + void dump(const Settings& s) const; private: - std::vector<Module> m_modules; - std::unique_ptr<WindowsInfo> m_windows; - std::vector<SecurityProduct> m_security; - std::unique_ptr<Metrics> m_metrics; + mutable std::vector<Module> m_modules; + mutable std::unique_ptr<WindowsInfo> m_windows; + mutable std::vector<SecurityProduct> m_security; + mutable std::unique_ptr<Metrics> m_metrics; + + // dumps all the disks involved in the settings + // + void dumpDisks(const Settings& s) const; }; +// environment variables +// +QString get(const QString& name); +QString set(const QString& name, const QString& value); + +QString path(); +QString addPath(const QString& s); +QString setPath(const QString& s); + + +class Service +{ +public: + enum class StartType + { + None = 0, + Disabled, + Enabled + }; + + enum class Status + { + None = 0, + Stopped, + Running + }; + + + explicit Service(QString name); + Service(QString name, StartType st, Status s); + + bool isValid() const; + + const QString& name() const; + StartType startType() const; + Status status() const; + + QString toString() const; + +private: + QString m_name; + StartType m_startType; + Status m_status; +}; + + +Service getService(const QString& name); +QString toString(Service::StartType st); +QString toString(Service::Status st); + enum class CoreDumpTypes { Mini = 1, @@ -142,7 +237,7 @@ enum class CoreDumpTypes Full }; -// creates a minidump file for the given process +// creates a minidump file for this process // bool coredump(CoreDumpTypes type); @@ -152,3 +247,5 @@ bool coredump(CoreDumpTypes type); bool coredumpOther(CoreDumpTypes type); } // namespace env + +#endif // ENV_ENV_H diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index b1b9bd2e..5fb80449 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -4,6 +4,7 @@ #include <shellscalingapi.h> #include <log.h> #include <utility.h> +#include <QDesktopWidget> namespace env { @@ -225,6 +226,17 @@ const std::vector<Display>& Metrics::displays() const return m_displays; } +QRect Metrics::desktopGeometry() const +{ + QRect r; + + for (auto* s : QGuiApplication::screens()) { + r = r.united(s->geometry()); + } + + return r; +} + void Metrics::getDisplays() { // don't bother if it goes over 100 diff --git a/src/envmetrics.h b/src/envmetrics.h index bede36fc..8dfdb087 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -1,3 +1,6 @@ +#ifndef ENV_METRICS_H +#define ENV_METRICS_H + #include <QString> #include <vector> @@ -63,6 +66,10 @@ public: // const std::vector<Display>& displays() const; + // full resolution + // + QRect desktopGeometry() const; + private: std::vector<Display> m_displays; @@ -70,3 +77,5 @@ private: }; } // namespace + +#endif // ENV_METRICS_H diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 8cea414a..3f1f8912 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -320,6 +320,40 @@ QString Module::getMD5() const } +Process::Process(DWORD pid, QString name) + : m_pid(pid), m_name(std::move(name)) +{ +} + +DWORD Process::pid() const +{ + return m_pid; +} + +const QString& Process::name() const +{ + return m_name; +} + +// whether this process can be accessed; fails if the current process doesn't +// have the proper permissions +// +bool Process::canAccess() const +{ + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid)); + + if (!h) { + const auto e = GetLastError(); + if (e == ERROR_ACCESS_DENIED) { + return false; + } + } + + return true; +} + + std::vector<Module> getLoadedModules() { HandlePtr snapshot(CreateToolhelp32Snapshot( @@ -373,4 +407,51 @@ std::vector<Module> getLoadedModules() return v; } + +std::vector<Process> getRunningProcesses() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); + return {}; + } + + PROCESSENTRY32 entry = {}; + entry.dwSize = sizeof(entry); + + // first process, this shouldn't fail because there's at least one process + // running + if (!Process32First(snapshot.get(), &entry)) { + const auto e = GetLastError(); + log::error("Process32First() failed, {}", formatSystemMessage(e)); + return {}; + } + + std::vector<Process> v; + + for (;;) + { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + // next process + if (!Process32Next(snapshot.get(), &entry)) + { + const auto e = GetLastError(); + + // no more processes is not an error + if (e != ERROR_NO_MORE_FILES) + log::error("Process32Next() failed, {}", formatSystemMessage(e)); + + break; + } + } + + return v; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index ea1156bd..deb7520f 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -1,3 +1,6 @@ +#ifndef ENV_MODULE_H +#define ENV_MODULE_H + #include <QString> #include <QDateTime> @@ -9,7 +12,7 @@ namespace env class Module { public: - explicit Module(QString path, std::size_t fileSize); + Module(QString path, std::size_t fileSize); // returns the module's path // @@ -93,6 +96,30 @@ private: }; +// represents one process +// +class Process +{ +public: + Process(DWORD pid, QString name); + + DWORD pid() const; + const QString& name() const; + + // whether this process can be accessed; fails if the current process doesn't + // have the proper permissions + // + bool canAccess() const; + +private: + DWORD m_pid; + QString m_name; +}; + + +std::vector<Process> getRunningProcesses(); std::vector<Module> getLoadedModules(); } // namespace env + +#endif // ENV_MODULE_H diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 376be4df..ffb17c42 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -9,6 +9,11 @@ #include <netfw.h> #pragma comment(lib, "Wbemuuid.lib") +#include <accctrl.h> +#include <aclapi.h> +#include <sddl.h> +#pragma comment(lib, "advapi32.lib") + namespace env { @@ -156,6 +161,11 @@ SecurityProduct::SecurityProduct( { } +const QUuid& SecurityProduct::guid() const +{ + return m_guid; +} + const QString& SecurityProduct::name() const { return m_name; @@ -180,7 +190,13 @@ QString SecurityProduct::toString() const { QString s; - s += m_name + " (" + providerToString() + ")"; + if (m_name.isEmpty()) { + s += "(no name)"; + } else { + s += m_name; + } + + s += " (" + providerToString() + ")"; if (!m_active) { s += ", inactive"; @@ -190,7 +206,9 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - if (!m_guid.isNull()) { + if (m_guid.isNull()) { + s += ", (no guid)"; + } else { s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); } @@ -237,91 +255,98 @@ QString SecurityProduct::providerToString() const } -std::vector<SecurityProduct> getSecurityProductsFromWMI() +std::optional<SecurityProduct> handleProduct(IWbemClassObject* o) { - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map<QUuid, SecurityProduct> map; + VARIANT prop; - auto handleProduct = [&](auto* o) { - VARIANT prop; - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessage(ret)); - return; - } + // guid + auto ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); + return {}; + } - if (prop.vt != VT_BSTR) { - log::error("displayName is a {}, not a bstr", prop.vt); - return; - } + if (prop.vt != VT_BSTR) { + log::error("instanceGuid is a {}, not a bstr", prop.vt); + return {}; + } - const std::wstring name = prop.bstrVal; - VariantClear(&prop); + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - log::error("productState is a {}, is not a VT_UI4", prop.vt); - return; - } + // display name + QString displayName; + ret = o->Get(L"displayName", 0, &prop, 0, 0); - DWORD state = 0; + if (FAILED(ret)) { + log::error("failed to get displayName, {}", formatSystemMessage(ret)); + } else if (prop.vt != VT_BSTR) { + log::error("displayName is a {}, not a bstr", prop.vt); + } else { + displayName = QString::fromWCharArray(prop.bstrVal); + } + + VariantClear(&prop); + + + // product state + DWORD state = 0; + ret = o->Get(L"productState", 0, &prop, 0, 0); + + if (FAILED(ret)) { + log::error("failed to get productState, {}", formatSystemMessage(ret)); + } else { if (prop.vt == VT_I4) { state = prop.lVal; - } else { + } else if (prop.vt == VT_UI4) { state = prop.ulVal; + } else if (prop.vt == VT_NULL) { + log::warn("productState is null"); + } else { + log::error("productState is a {}, not a VT_I4 or a VT_UI4", prop.vt); } + } - VariantClear(&prop); + VariantClear(&prop); - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_BSTR) { - log::error("instanceGuid is a {}, is not a bstr", prop.vt); - return; - } + const auto provider = static_cast<int>((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); - const auto provider = static_cast<int>((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; + return SecurityProduct(guid, displayName, provider, active, upToDate); +} - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); +std::vector<SecurityProduct> getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map<QUuid, SecurityProduct> map; - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); + auto f = [&](auto* o) { + if (auto p=handleProduct(o)) { + map.emplace(p->guid(), std::move(*p)); + } }; { WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } { WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } std::vector<SecurityProduct> v; @@ -397,4 +422,331 @@ std::vector<SecurityProduct> getSecurityProducts() return v; } + +class failed +{ +public: + failed(DWORD e, QString what) + : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e))) + { + } + + QString what() const + { + return m_what; + } + +private: + QString m_what; +}; + + +MallocPtr<SECURITY_DESCRIPTOR> getSecurityDescriptor(const QString& path) +{ + const auto wpath = path.toStdWString(); + BOOL ret = FALSE; + + DWORD length = 0; + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + nullptr, 0, &length); + + if (!ret || length == 0) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + if (e == ERROR_ACCESS_DENIED) { + // if this fails, the user doesn't even have permissions to get the + // security descriptor, which probably means they're not the owner and + // their effective access is none + throw failed(e, "cannot get security descriptor"); + } else { + // other error + throw failed(e, "GetFileSecurity() for length failed"); + } + } + } + + MallocPtr<SECURITY_DESCRIPTOR> sd( + static_cast<SECURITY_DESCRIPTOR*>(std::malloc(length))); + + std::memset(sd.get(), 0, length); + + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + sd.get(), length, &length); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetFileSecurity()"); + } + + return sd; +} + +PACL getDacl(SECURITY_DESCRIPTOR* sd) +{ + BOOL present = FALSE; + BOOL daclDefaulted = FALSE; + PACL acl = nullptr; + + BOOL ret = ::GetSecurityDescriptorDacl(sd, &present, &acl, &daclDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptorDacl()"); + } + + if (!present) { + return nullptr; + } + + return acl; +} + +PSID getFileOwner(SECURITY_DESCRIPTOR* sd) +{ + BOOL ownerDefaulted = FALSE; + PSID owner; + + BOOL ret = ::GetSecurityDescriptorOwner(sd, &owner, &ownerDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptionOwner()"); + } + + return owner; +} + +MallocPtr<void> getCurrentUser() +{ + HANDLE hnd = ::GetCurrentProcess(); + HANDLE rawToken = 0; + + BOOL ret = ::OpenProcessToken(hnd, TOKEN_QUERY, &rawToken); + if (!ret) { + const auto e = GetLastError(); + throw(e, "OpenProcessToken()"); + } + + HandlePtr token(rawToken); + + DWORD retsize = 0; + ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize); + + if (!ret) { + const auto e = GetLastError(); + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "GetTokenInformation() for length"); + } + } + + MallocPtr<void> tokenBuffer(std::malloc(retsize)); + ret = ::GetTokenInformation( + token.get(), TokenUser, tokenBuffer.get(), retsize, &retsize); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetTokenInformation()"); + } + + PSID tokenSid = ((PTOKEN_USER)(tokenBuffer.get()))->User.Sid; + DWORD sidLen = ::GetLengthSid(tokenSid); + MallocPtr<void> currentUserSID((SID*)(malloc(sidLen))); + + ret = ::CopySid(sidLen, currentUserSID.get(), tokenSid); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "CopySid()"); + } + + return currentUserSID; +} + +ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid) +{ + TRUSTEEW trustee = {}; + BuildTrusteeWithSid(&trustee, sid); + + ACCESS_MASK access = 0; + DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access); + + if (ret != ERROR_SUCCESS) { + throw failed(ret, "GetEffectiveRightsFromAclW()"); + } + + return access; +} + +QString getUsername(PSID owner) +{ + DWORD nameSize=0, domainSize=0; + auto use = SidTypeUnknown; + + BOOL ret = LookupAccountSidW( + nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "LookupAccountSid() for sizes"); + } + } + + auto wsName = std::make_unique<wchar_t[]>(nameSize); + auto wsDomain = std::make_unique<wchar_t[]>(domainSize); + + ret = LookupAccountSidW( + nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "LookupAccountSid()"); + } + + const QString name = QString::fromWCharArray(wsName.get(), nameSize); + const QString domain = QString::fromWCharArray(wsDomain.get(), domainSize); + + if (!name.isEmpty() && !domain.isEmpty()) { + return domain + "\\" + name; + } else { + // either or both are empty + return name + domain; + } +} + +FileRights makeFileRights(ACCESS_MASK m) +{ + FileRights fr; + + if (m & FILE_GENERIC_READ) { + fr.list.push_back("file_generic_read"); + } else { + if (m & READ_CONTROL) { + fr.list.push_back("read_ctrl"); + } + + if (m & FILE_READ_DATA) { + fr.list.push_back("read_data"); + } + + if (m & FILE_READ_ATTRIBUTES) { + fr.list.push_back("read_atts"); + } + + if (m & FILE_READ_EA) { + fr.list.push_back("read_ex_atts"); + } + + if (m & SYNCHRONIZE) { + fr.list.push_back("sync"); + } + } + + if (m & FILE_GENERIC_WRITE) { + fr.list.push_back("file_generic_write"); + } else { + // READ_CONTROL handled above + + if (m & FILE_WRITE_DATA) { + fr.list.push_back("write_data"); + } + + if (m & FILE_WRITE_ATTRIBUTES) { + fr.list.push_back("write_atts"); + } + + if (m & FILE_WRITE_EA) { + fr.list.push_back("write_ex_atts"); + } + + if (m & FILE_APPEND_DATA) { + fr.list.push_back("append_data"); + } + + // SYNCHRONIZE handled above + } + + if (m & FILE_GENERIC_EXECUTE) { + fr.list.push_back("file_generic_execute"); + fr.hasExecute = true; + } else { + // READ_CONTROL handled above + // FILE_READ_ATTRIBUTES handled above + + if (m & FILE_EXECUTE) { + fr.list.push_back("execute"); + fr.hasExecute = true; + } + + // SYNCHRONIZE handled above + } + + if (m & DELETE) { + fr.list.push_back("delete"); + } + + if (m & WRITE_DAC) { + fr.list.push_back("write_dac"); + } + + if (m & WRITE_OWNER) { + fr.list.push_back("write_owner"); + } + + if (m & GENERIC_ALL) { + fr.list.push_back("generic_all"); + } + + if (m & GENERIC_WRITE) { + fr.list.push_back("generic_write"); + } + + if (m & GENERIC_READ) { + fr.list.push_back("generic_read"); + } + + // 0x001f01ff + const auto normalRights = + STANDARD_RIGHTS_ALL | + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | + FILE_DELETE_CHILD; + + if (m == normalRights) { + fr.normalRights = true; + } + + return fr; +} + +FileSecurity getFileSecurity(const QString& path) +{ + FileSecurity fs; + + try + { + auto sd = getSecurityDescriptor(path); + auto dacl = getDacl(sd.get()); + auto currentUser = getCurrentUser(); + auto owner = getFileOwner(sd.get()); + auto access = getEffectiveRights(dacl, currentUser.get()); + + fs.rights = makeFileRights(access); + + if (EqualSid(owner, currentUser.get())) { + fs.owner = "(this user)"; + } else { + fs.owner = getUsername(owner); + } + } + catch(failed& f) + { + fs.error = f.what(); + } + + return fs; +} } // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h index 200cb531..5f9e5332 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -1,3 +1,6 @@ +#ifndef ENV_SECURITY_H +#define ENV_SECURITY_H + #include <QUuid> #include <QString> @@ -13,6 +16,10 @@ public: QUuid guid, QString name, int provider, bool active, bool upToDate); + // guid + // + const QUuid& guid() const; + // display name of the product // const QString& name() const; @@ -46,4 +53,23 @@ private: std::vector<SecurityProduct> getSecurityProducts(); + +struct FileRights +{ + QStringList list; + bool hasExecute = false; + bool normalRights = false; +}; + +struct FileSecurity +{ + QString owner; + FileRights rights; + QString error; +}; + +FileSecurity getFileSecurity(const QString& file); + } // namespace env + +#endif // ENV_SECURITY_H diff --git a/src/envshortcut.h b/src/envshortcut.h index 82eea191..a05528d9 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -1,3 +1,6 @@ +#ifndef ENV_SHORTCUT_H +#define ENV_SHORTCUT_H + #include <QString> class Executable; @@ -103,3 +106,5 @@ private: QString toString(Shortcut::Locations loc); } // namespace + +#endif // ENV_SHORTCUT_H diff --git a/src/envwindows.h b/src/envwindows.h index c23f99f4..90655e49 100644 --- a/src/envwindows.h +++ b/src/envwindows.h @@ -1,3 +1,6 @@ +#ifndef ENV_WINDOWS_H +#define ENV_WINDOWS_H + #include <QString> #include <optional> @@ -104,3 +107,5 @@ private: }; } // namespace + +#endif // ENV_WINDOWS_H diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp deleted file mode 100644 index a9d045a5..00000000 --- a/src/expanderwidget.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "expanderwidget.h" - -ExpanderWidget::ExpanderWidget() - : m_button(nullptr), m_content(nullptr), opened_(false) -{ -} - -ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) - : ExpanderWidget() -{ - set(button, content); -} - -void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) -{ - m_button = button; - m_content = content; - - m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); - - toggle(o); -} - -void ExpanderWidget::toggle() -{ - if (opened()) { - toggle(false); - } - else { - toggle(true); - } -} - -void ExpanderWidget::toggle(bool b) -{ - if (b) { - m_button->setArrowType(Qt::DownArrow); - m_content->show(); - } else { - m_button->setArrowType(Qt::RightArrow); - m_content->hide(); - } - - // the state has to be remembered instead of using m_content's visibility - // because saving the state in saveConflictExpandersState() happens after the - // dialog is closed, which marks all the widgets hidden - opened_ = b; -} - -bool ExpanderWidget::opened() const -{ - return opened_; -} - -QByteArray ExpanderWidget::saveState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream << opened(); - - return result; -} - -void ExpanderWidget::restoreState(const QByteArray& a) -{ - QDataStream stream(a); - - bool opened = false; - stream >> opened; - - if (stream.status() == QDataStream::Ok) { - toggle(opened); - } -} - -QToolButton* ExpanderWidget::button() const -{ - return m_button; -} diff --git a/src/expanderwidget.h b/src/expanderwidget.h deleted file mode 100644 index 99b2d303..00000000 --- a/src/expanderwidget.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef EXPANDERWIDGET_H -#define EXPANDERWIDGET_H - -#include <QToolButton> - -/* Takes a QToolButton and a widget and creates an expandable widget. -**/ -class ExpanderWidget -{ -public: - /** empty expander, use set() - **/ - ExpanderWidget(); - - /** see set() - **/ - ExpanderWidget(QToolButton* button, QWidget* content); - - /** @brief sets the button and content widgets to use - * the button will be given an arrow icon, clicking it will toggle the - * visibility of the given widget - * @param button the button that toggles the content - * @param content the widget that will be shown or hidden - * @param opened initial state, defaults to closed - **/ - void set(QToolButton* button, QWidget* content, bool opened=false); - - /** either opens or closes the expander depending on the current state - **/ - void toggle(); - - /** sets the current state of the expander - **/ - void toggle(bool b); - - /** returns whether the expander is currently opened - **/ - bool opened() const; - - QByteArray saveState() const; - void restoreState(const QByteArray& a); - - QToolButton* button() const; - -private: - QToolButton* m_button; - QWidget* m_content; - bool opened_; -}; - -#endif // EXPANDERWIDGET_H diff --git a/src/helper.cpp b/src/helper.cpp deleted file mode 100644 index 59a2d3d1..00000000 --- a/src/helper.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "helper.h"
-#include "utility.h"
-#include <report.h>
-#include <LMCons.h>
-
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
-#include <QDir>
-#include <QApplication>
-
-using MOBase::reportError;
-
-
-namespace Helper {
-
-
-static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine, BOOL async)
-{
- wchar_t fileName[MAX_PATH];
- _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory);
-
- SHELLEXECUTEINFOW execInfo = {0};
-
- execInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
- execInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
- execInfo.hwnd = nullptr;
- execInfo.lpVerb = L"runas";
- execInfo.lpFile = fileName;
- execInfo.lpParameters = commandLine;
- execInfo.lpDirectory = moDirectory;
- execInfo.nShow = SW_SHOW;
-
- ::ShellExecuteExW(&execInfo);
-
- if (execInfo.hProcess == 0) {
- reportError(QObject::tr("helper failed"));
- return false;
- }
-
- if (async) {
- return true;
- }
-
- if (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
- reportError(QObject::tr("helper failed"));
- return false;
- }
-
- DWORD exitCode;
- GetExitCodeProcess(execInfo.hProcess, &exitCode);
- return exitCode == NOERROR;
-}
-
-
-bool init(const std::wstring &moPath, const std::wstring &dataPath)
-{
- DWORD userNameLen = UNLEN + 1;
- wchar_t userName[UNLEN + 1];
-
- if (!GetUserName(userName, &userNameLen)) {
- reportError(QObject::tr("failed to determine account name"));
- return false;
- }
- wchar_t *commandLine = new wchar_t[32768];
-
- _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"",
- dataPath.c_str(), userName);
-
- bool res = helperExec(moPath.c_str(), commandLine, FALSE);
- delete [] commandLine;
-
- return res;
-}
-
-
-bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath)
-{
- wchar_t *commandLine = new wchar_t[32768];
- _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"",
- dataPath.c_str());
-
- bool res = helperExec(moPath.c_str(), commandLine, FALSE);
- delete [] commandLine;
-
- return res;
-}
-
-
-bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir)
-{
- wchar_t *commandLine = new wchar_t[32768];
- _snwprintf(commandLine, 32768, L"adminLaunch %d \"%ls\" \"%ls\"",
- ::GetCurrentProcessId(),
- moFile.c_str(),
- workingDir.c_str()
- );
-
- bool res = helperExec(moPath.c_str(), commandLine, TRUE);
- delete [] commandLine;
-
- return res;
-}
-
-
-} // namespace
diff --git a/src/helper.h b/src/helper.h deleted file mode 100644 index f6667a84..00000000 --- a/src/helper.h +++ /dev/null @@ -1,64 +0,0 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef HELPER_H
-#define HELPER_H
-
-
-#include <string>
-
-
-/**
- * @brief Convenience functions to work with the external helper program.
- *
- * The mo_helper program is used to make changes on the system that require administrative
- * rights, so that ModOrganizer itself can run without special privileges
- **/
-namespace Helper {
-
-/**
- * @brief initialise the specified directory for use with mod organizer.
- *
- * This will create all required sub-directories and give the user running ModOrganizer
- * write-access
- *
- * @param moPath absolute path to the ModOrganizer base directory
- * @return true on success
- **/
-bool init(const std::wstring &moPath, const std::wstring &dataPath);
-
-/**
- * @brief sets the last modified time for all .bsa-files in the target directory well into the past
- * @param moPath absolute path to the modOrganizer base directory
- * @param dataPath the path taht contains the .bsa-files, usually the data directory of the game
- **/
-bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath);
-
-/**
- * @brief waits for the current process to exit and restarts it as an administrator
- * @param moPath absolute path to the modOrganizer base directory
- * @param moFile file name of modOrganizer
- * @param workingDir current working directory
- **/
-bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir);
-
-}
-
-
-#endif // HELPER_H
diff --git a/src/main.cpp b/src/main.cpp index b5568fec..ba988ae3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "executableslist.h" #include "singleinstance.h" #include "utility.h" -#include "helper.h" #include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" @@ -577,8 +576,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; - - env.dump(); + env.dump(settings); settings.dump(); sanityChecks(env); @@ -649,7 +647,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, game->setGameVariant(edition); - log::info("managing game at {}", game->gameDirectory().absolutePath()); + log::info( + "using game plugin '{}' ('{}', steam id '{}') at {}", + game->gameName(), game->gameShortName(), game->steamAPPId(), + game->gameDirectory().absolutePath()); organizer.updateExecutablesList(); @@ -825,7 +826,12 @@ void initLogging() { LogModel::create(); - log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + log::LoggerConfiguration conf; + conf.maxLevel = MOBase::log::Debug; + conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; + conf.utc = true; + + log::createDefault(conf); log::getDefault().setCallback( [](log::Entry e){ LogModel::instance().add(e); }); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bd9f1486..42cbe919 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2565,17 +2565,6 @@ void MainWindow::modInstalled(const QString &modName) modUpdateCheck(IDs); } -void MainWindow::procError(QProcess::ProcessError error) -{ - reportError(tr("failed to spawn notepad.exe: %1").arg(error)); - this->sender()->deleteLater(); -} - -void MainWindow::procFinished(int, QProcess::ExitStatus) -{ - this->sender()->deleteLater(); -} - void MainWindow::showMessage(const QString &message) { MessageDialog::showMessage(message, this); @@ -6449,11 +6438,13 @@ void MainWindow::on_bossButton_clicked() return; } - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), - parameters.join(" "), - qApp->applicationDirPath() + "/loot", - true, - stdOutWrite); + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.stdOut = stdOutWrite; + + HANDLE loot = spawn::startBinary(this, sp); // we don't use the write end ::CloseHandle(stdOutWrite); diff --git a/src/mainwindow.h b/src/mainwindow.h index 6f06b9d5..1f997ab1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -489,9 +489,6 @@ private slots: void doMoveOverwriteContentToMod(const QString &modAbsolutePath); void clearOverwrite(); - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - // nexus related void checkModsForUpdates(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a77c2ac9..ad305dfc 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -33,7 +33,7 @@ signals: private: struct Expanders { - ExpanderWidget overwrite, overwritten, nonconflict; + MOBase::ExpanderWidget overwrite, overwritten, nonconflict; }; ConflictsTab* m_tab; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1aa831a0..465b0c87 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -173,17 +173,17 @@ p, li { white-space: pre-wrap; } <context> <name>AdvancedConflictListModel</name> <message> - <location filename="modinfodialogconflicts.cpp" line="334"/> + <location filename="modinfodialogconflicts.cpp" line="333"/> <source>Overwrites</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="335"/> + <location filename="modinfodialogconflicts.cpp" line="334"/> <source>File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="336"/> + <location filename="modinfodialogconflicts.cpp" line="335"/> <source>Overwritten By</source> <translation type="unfinished"></translation> </message> @@ -201,12 +201,12 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="browserdialog.cpp" line="94"/> + <location filename="browserdialog.cpp" line="95"/> <source>new</source> <translation type="unfinished"></translation> </message> <message> - <location filename="browserdialog.cpp" line="207"/> + <location filename="browserdialog.cpp" line="208"/> <source>failed to start download</source> <translation type="unfinished"></translation> </message> @@ -276,12 +276,12 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="categoriesdialog.cpp" line="239"/> + <location filename="categoriesdialog.cpp" line="246"/> <source>Add</source> <translation type="unfinished"></translation> </message> <message> - <location filename="categoriesdialog.cpp" line="240"/> + <location filename="categoriesdialog.cpp" line="247"/> <source>Remove</source> <translation type="unfinished"></translation> </message> @@ -289,32 +289,32 @@ p, li { white-space: pre-wrap; } <context> <name>ConflictsTab</name> <message> - <location filename="modinfodialogconflicts.cpp" line="695"/> + <location filename="modinfodialogconflicts.cpp" line="700"/> <source>&Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="700"/> + <location filename="modinfodialogconflicts.cpp" line="705"/> <source>&Unhide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="703"/> + <location filename="modinfodialogconflicts.cpp" line="708"/> <source>&Open/Execute</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="706"/> + <location filename="modinfodialogconflicts.cpp" line="711"/> <source>&Preview</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="709"/> + <location filename="modinfodialogconflicts.cpp" line="714"/> <source>Open in &Explorer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="712"/> + <location filename="modinfodialogconflicts.cpp" line="717"/> <source>&Go to...</source> <translation type="unfinished"></translation> </message> @@ -368,114 +368,114 @@ p, li { white-space: pre-wrap; } <context> <name>DownloadList</name> <message> - <location filename="downloadlist.cpp" line="71"/> + <location filename="downloadlist.cpp" line="73"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="72"/> + <location filename="downloadlist.cpp" line="74"/> <source>Mod name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="73"/> + <location filename="downloadlist.cpp" line="75"/> <source>Version</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="74"/> + <location filename="downloadlist.cpp" line="76"/> <source>Nexus ID</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="75"/> + <location filename="downloadlist.cpp" line="77"/> <source>Size</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="76"/> + <location filename="downloadlist.cpp" line="78"/> <source>Status</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="77"/> + <location filename="downloadlist.cpp" line="79"/> <source>Filetime</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="92"/> + <location filename="downloadlist.cpp" line="94"/> <source>< game %1 mod %2 file %3 ></source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="93"/> + <location filename="downloadlist.cpp" line="95"/> <source>Unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="94"/> + <location filename="downloadlist.cpp" line="96"/> <source>Pending</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="128"/> + <location filename="downloadlist.cpp" line="130"/> <source>Started</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="129"/> + <location filename="downloadlist.cpp" line="131"/> <source>Canceling</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="130"/> + <location filename="downloadlist.cpp" line="132"/> <source>Pausing</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="131"/> + <location filename="downloadlist.cpp" line="133"/> <source>Canceled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="132"/> + <location filename="downloadlist.cpp" line="134"/> <source>Paused</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="133"/> + <location filename="downloadlist.cpp" line="135"/> <source>Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="134"/> - <location filename="downloadlist.cpp" line="135"/> <location filename="downloadlist.cpp" line="136"/> + <location filename="downloadlist.cpp" line="137"/> + <location filename="downloadlist.cpp" line="138"/> <source>Fetching Info</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="137"/> + <location filename="downloadlist.cpp" line="139"/> <source>Downloaded</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="138"/> + <location filename="downloadlist.cpp" line="140"/> <source>Installed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="139"/> + <location filename="downloadlist.cpp" line="141"/> <source>Uninstalled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="157"/> + <location filename="downloadlist.cpp" line="159"/> <source>Pending download</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlist.cpp" line="161"/> + <location filename="downloadlist.cpp" line="163"/> <source>Information missing, please select "Query Info" from the context menu to re-retrieve.</source> <translation type="unfinished"></translation> </message> @@ -483,156 +483,156 @@ p, li { white-space: pre-wrap; } <context> <name>DownloadListWidget</name> <message> - <location filename="downloadlistwidget.cpp" line="210"/> + <location filename="downloadlistwidget.cpp" line="216"/> <source>Install</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="212"/> + <location filename="downloadlistwidget.cpp" line="218"/> <source>Query Info</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="214"/> + <location filename="downloadlistwidget.cpp" line="220"/> <source>Visit on Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="215"/> + <location filename="downloadlistwidget.cpp" line="221"/> <source>Open File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="216"/> - <location filename="downloadlistwidget.cpp" line="228"/> - <location filename="downloadlistwidget.cpp" line="233"/> + <location filename="downloadlistwidget.cpp" line="222"/> + <location filename="downloadlistwidget.cpp" line="234"/> + <location filename="downloadlistwidget.cpp" line="239"/> <source>Show in Folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="220"/> - <location filename="downloadlistwidget.cpp" line="231"/> + <location filename="downloadlistwidget.cpp" line="226"/> + <location filename="downloadlistwidget.cpp" line="237"/> <source>Delete</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="222"/> + <location filename="downloadlistwidget.cpp" line="228"/> <source>Un-Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="224"/> + <location filename="downloadlistwidget.cpp" line="230"/> <source>Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="226"/> + <location filename="downloadlistwidget.cpp" line="232"/> <source>Cancel</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="227"/> + <location filename="downloadlistwidget.cpp" line="233"/> <source>Pause</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="232"/> + <location filename="downloadlistwidget.cpp" line="238"/> <source>Resume</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="238"/> + <location filename="downloadlistwidget.cpp" line="250"/> <source>Delete Installed Downloads...</source> <oldsource>Delete Installed...</oldsource> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="239"/> + <location filename="downloadlistwidget.cpp" line="251"/> <source>Delete Uninstalled Downloads...</source> <oldsource>Delete Uninstalled...</oldsource> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="240"/> + <location filename="downloadlistwidget.cpp" line="252"/> <source>Delete All Downloads...</source> <oldsource>Delete All...</oldsource> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="244"/> + <location filename="downloadlistwidget.cpp" line="256"/> <source>Hide Installed...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="245"/> + <location filename="downloadlistwidget.cpp" line="257"/> <source>Hide Uninstalled...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="246"/> + <location filename="downloadlistwidget.cpp" line="258"/> <source>Hide All...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="248"/> + <location filename="downloadlistwidget.cpp" line="260"/> <source>Un-Hide All...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="271"/> - <location filename="downloadlistwidget.cpp" line="326"/> - <location filename="downloadlistwidget.cpp" line="335"/> - <location filename="downloadlistwidget.cpp" line="344"/> + <location filename="downloadlistwidget.cpp" line="283"/> + <location filename="downloadlistwidget.cpp" line="338"/> + <location filename="downloadlistwidget.cpp" line="347"/> + <location filename="downloadlistwidget.cpp" line="356"/> <source>Delete Files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="272"/> + <location filename="downloadlistwidget.cpp" line="284"/> <source>This will permanently delete the selected download. Are you absolutely sure you want to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="327"/> + <location filename="downloadlistwidget.cpp" line="339"/> <source>This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="336"/> + <location filename="downloadlistwidget.cpp" line="348"/> <source>This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="345"/> + <location filename="downloadlistwidget.cpp" line="357"/> <source>This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="353"/> - <location filename="downloadlistwidget.cpp" line="362"/> - <location filename="downloadlistwidget.cpp" line="371"/> + <location filename="downloadlistwidget.cpp" line="365"/> + <location filename="downloadlistwidget.cpp" line="374"/> + <location filename="downloadlistwidget.cpp" line="383"/> <source>Hide Files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="354"/> + <location filename="downloadlistwidget.cpp" line="366"/> <source>This will remove all finished downloads from this list (but NOT from disk).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="363"/> + <location filename="downloadlistwidget.cpp" line="375"/> <source>This will remove all installed downloads from this list (but NOT from disk).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadlistwidget.cpp" line="372"/> + <location filename="downloadlistwidget.cpp" line="384"/> <source>This will remove all uninstalled downloads from this list (but NOT from disk).</source> <translation type="unfinished"></translation> </message> @@ -645,37 +645,37 @@ Are you absolutely sure you want to proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="390"/> + <location filename="downloadmanager.cpp" line="381"/> <source>Memory allocation error (in refreshing directory).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="490"/> + <location filename="downloadmanager.cpp" line="481"/> <source>failed to download %1: could not open output file: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="513"/> + <location filename="downloadmanager.cpp" line="504"/> <source>Download again?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="513"/> + <location filename="downloadmanager.cpp" line="504"/> <source>A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="568"/> + <location filename="downloadmanager.cpp" line="559"/> <source>Wrong Game</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="568"/> + <location filename="downloadmanager.cpp" line="559"/> <source>The download link is for a mod for "%1" but this instance of MO has been set up for "%2".</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="576"/> + <location filename="downloadmanager.cpp" line="567"/> <source>There is already a download queued for this file. Mod %1 @@ -683,12 +683,12 @@ File %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="582"/> + <location filename="downloadmanager.cpp" line="574"/> <source>Already Queued</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="591"/> + <location filename="downloadmanager.cpp" line="583"/> <source>There is already a download started for this file. Mod %1: %2 @@ -696,266 +696,266 @@ File %3: %4</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="626"/> + <location filename="downloadmanager.cpp" line="618"/> <source>Already Started</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="655"/> - <location filename="downloadmanager.cpp" line="788"/> + <location filename="downloadmanager.cpp" line="647"/> + <location filename="downloadmanager.cpp" line="780"/> <source>remove: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="674"/> + <location filename="downloadmanager.cpp" line="666"/> <source>failed to delete %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="681"/> + <location filename="downloadmanager.cpp" line="673"/> <source>failed to delete meta file for %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="741"/> + <location filename="downloadmanager.cpp" line="733"/> <source>restore: invalid download index: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="810"/> + <location filename="downloadmanager.cpp" line="802"/> <source>cancel: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="823"/> + <location filename="downloadmanager.cpp" line="815"/> <source>pause: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="845"/> + <location filename="downloadmanager.cpp" line="837"/> <source>resume: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="856"/> + <location filename="downloadmanager.cpp" line="848"/> <source>resume (int): invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="880"/> + <location filename="downloadmanager.cpp" line="872"/> <source>No known download urls. Sorry, this download can't be resumed.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="921"/> - <location filename="downloadmanager.cpp" line="973"/> + <location filename="downloadmanager.cpp" line="913"/> + <location filename="downloadmanager.cpp" line="965"/> <source>query: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="943"/> + <location filename="downloadmanager.cpp" line="935"/> <source>Please enter the nexus mod id</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="943"/> + <location filename="downloadmanager.cpp" line="935"/> <source>Mod ID:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="954"/> + <location filename="downloadmanager.cpp" line="946"/> <source>Please select the source game code for %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1013"/> + <location filename="downloadmanager.cpp" line="1005"/> <source>VisitNexus: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1034"/> + <location filename="downloadmanager.cpp" line="1026"/> <source>Nexus ID for this Mod is unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1041"/> + <location filename="downloadmanager.cpp" line="1033"/> <source>OpenFile: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1058"/> + <location filename="downloadmanager.cpp" line="1050"/> <source>OpenFileInDownloadsFolder: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1093"/> + <location filename="downloadmanager.cpp" line="1085"/> <source>get pending: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1102"/> + <location filename="downloadmanager.cpp" line="1094"/> <source>get path: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1111"/> + <location filename="downloadmanager.cpp" line="1103"/> <source>Main</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1112"/> + <location filename="downloadmanager.cpp" line="1104"/> <source>Update</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1113"/> + <location filename="downloadmanager.cpp" line="1105"/> <source>Optional</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1114"/> + <location filename="downloadmanager.cpp" line="1106"/> <source>Old</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1115"/> + <location filename="downloadmanager.cpp" line="1107"/> <source>Miscellaneous</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1116"/> + <location filename="downloadmanager.cpp" line="1108"/> <source>Deleted</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1117"/> + <location filename="downloadmanager.cpp" line="1109"/> <source>Unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1124"/> + <location filename="downloadmanager.cpp" line="1116"/> <source>display name: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1144"/> + <location filename="downloadmanager.cpp" line="1136"/> <source>file name: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1153"/> + <location filename="downloadmanager.cpp" line="1145"/> <source>file time: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1167"/> + <location filename="downloadmanager.cpp" line="1164"/> <source>file size: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1177"/> + <location filename="downloadmanager.cpp" line="1174"/> <source>progress: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1187"/> + <location filename="downloadmanager.cpp" line="1184"/> <source>state: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1197"/> + <location filename="downloadmanager.cpp" line="1194"/> <source>infocomplete: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1212"/> - <location filename="downloadmanager.cpp" line="1220"/> + <location filename="downloadmanager.cpp" line="1209"/> + <location filename="downloadmanager.cpp" line="1217"/> <source>mod id: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1228"/> + <location filename="downloadmanager.cpp" line="1225"/> <source>ishidden: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1237"/> + <location filename="downloadmanager.cpp" line="1234"/> <source>file info: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1247"/> + <location filename="downloadmanager.cpp" line="1244"/> <source>mark installed: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1292"/> + <location filename="downloadmanager.cpp" line="1289"/> <source>mark uninstalled: invalid download index %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1469"/> + <location filename="downloadmanager.cpp" line="1455"/> <source>Memory allocation error (in processing progress event).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1479"/> + <location filename="downloadmanager.cpp" line="1465"/> <source>Memory allocation error (in processing downloaded data).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1595"/> + <location filename="downloadmanager.cpp" line="1581"/> <source>Information updated</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1597"/> - <location filename="downloadmanager.cpp" line="1612"/> + <location filename="downloadmanager.cpp" line="1583"/> + <location filename="downloadmanager.cpp" line="1598"/> <source>No matching file found on Nexus! Maybe this file is no longer available or it was renamed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1599"/> + <location filename="downloadmanager.cpp" line="1585"/> <source>No file on Nexus matches the selected file by name. Please manually choose the correct one.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1727"/> + <location filename="downloadmanager.cpp" line="1730"/> <source>No download server available. Please try again later.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1892"/> + <location filename="downloadmanager.cpp" line="1900"/> <source>Failed to request file info from nexus: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1919"/> + <location filename="downloadmanager.cpp" line="1927"/> <source>Warning: Content type is: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1924"/> + <location filename="downloadmanager.cpp" line="1932"/> <source>Download header content length: %1 downloaded file size: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1926"/> + <location filename="downloadmanager.cpp" line="1934"/> <source>Download failed: %1 (%2)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="1948"/> + <location filename="downloadmanager.cpp" line="1956"/> <source>We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="2031"/> + <location filename="downloadmanager.cpp" line="2039"/> <source>failed to re-open %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="downloadmanager.cpp" line="2072"/> + <location filename="downloadmanager.cpp" line="2084"/> <source>Unable to write download to drive (return %1). Check the drive's available storage. @@ -1147,7 +1147,8 @@ Right now the only case I know of where this needs to be overwritten is for the </message> <message> <location filename="editexecutablesdialog.ui" line="431"/> - <source>Use Application's Icon for shortcuts</source> + <source>Use Application's Icon for desktop shortcuts</source> + <oldsource>Use Application's Icon for shortcuts</oldsource> <translation type="unfinished"></translation> </message> <message> @@ -1156,42 +1157,43 @@ Right now the only case I know of where this needs to be overwritten is for the <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="437"/> + <location filename="editexecutablesdialog.cpp" line="441"/> <source>Reset plugin executables</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="439"/> + <location filename="editexecutablesdialog.cpp" line="443"/> <source>This will restore all the executables provided by the game plugin. If there are existing executables with the same names, they will be automatically renamed and left unchanged.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="458"/> + <location filename="editexecutablesdialog.cpp" line="462"/> + <location filename="editexecutablesdialog.cpp" line="624"/> <source>New Executable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="602"/> + <location filename="editexecutablesdialog.cpp" line="606"/> <source>Select a binary</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="603"/> + <location filename="editexecutablesdialog.cpp" line="607"/> <source>Executable (%1)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="634"/> + <location filename="editexecutablesdialog.cpp" line="639"/> <source>Select a directory</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="683"/> + <location filename="editexecutablesdialog.cpp" line="688"/> <source>Java (32-bit) required</source> <translation type="unfinished"></translation> </message> <message> - <location filename="editexecutablesdialog.cpp" line="684"/> + <location filename="editexecutablesdialog.cpp" line="689"/> <source>MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary.</source> <translation type="unfinished"></translation> </message> @@ -1199,73 +1201,73 @@ Right now the only case I know of where this needs to be overwritten is for the <context> <name>FileTreeTab</name> <message> - <location filename="modinfodialogfiletree.cpp" line="24"/> + <location filename="modinfodialogfiletree.cpp" line="25"/> <source>&New Folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="25"/> + <location filename="modinfodialogfiletree.cpp" line="26"/> <source>&Open/Execute</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="26"/> + <location filename="modinfodialogfiletree.cpp" line="27"/> <source>&Preview</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="27"/> + <location filename="modinfodialogfiletree.cpp" line="28"/> <source>Open in &Explorer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="28"/> + <location filename="modinfodialogfiletree.cpp" line="29"/> <source>&Rename</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="29"/> + <location filename="modinfodialogfiletree.cpp" line="30"/> <source>&Delete</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="30"/> + <location filename="modinfodialogfiletree.cpp" line="31"/> <source>&Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="31"/> + <location filename="modinfodialogfiletree.cpp" line="32"/> <source>&Unhide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="103"/> - <location filename="modinfodialogfiletree.cpp" line="109"/> + <location filename="modinfodialogfiletree.cpp" line="104"/> + <location filename="modinfodialogfiletree.cpp" line="110"/> <source>New Folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="115"/> + <location filename="modinfodialogfiletree.cpp" line="116"/> <source>Failed to create "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="180"/> + <location filename="modinfodialogfiletree.cpp" line="181"/> <source>Are you sure you want to delete "%1"?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="182"/> + <location filename="modinfodialogfiletree.cpp" line="183"/> <source>Are you sure you want to delete the selected files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="185"/> + <location filename="modinfodialogfiletree.cpp" line="186"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogfiletree.cpp" line="220"/> + <location filename="modinfodialogfiletree.cpp" line="221"/> <source>Failed to delete %1</source> <translation type="unfinished"></translation> </message> @@ -1405,83 +1407,83 @@ Right now the only case I know of where this needs to be overwritten is for the <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="489"/> + <location filename="installationmanager.cpp" line="494"/> <source>failed to create backup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="498"/> + <location filename="installationmanager.cpp" line="503"/> <source>Mod Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="498"/> + <location filename="installationmanager.cpp" line="503"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="551"/> + <location filename="installationmanager.cpp" line="556"/> <source>Invalid name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="552"/> + <location filename="installationmanager.cpp" line="557"/> <source>The name you entered is invalid, please enter a different one.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="720"/> + <location filename="installationmanager.cpp" line="725"/> <source>File format "%1" not supported</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="884"/> + <location filename="installationmanager.cpp" line="888"/> <source>None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="896"/> + <location filename="installationmanager.cpp" line="900"/> <source>no error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="899"/> + <location filename="installationmanager.cpp" line="903"/> <source>7z.dll not found</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="902"/> + <location filename="installationmanager.cpp" line="906"/> <source>7z.dll isn't valid</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="905"/> + <location filename="installationmanager.cpp" line="909"/> <source>archive not found</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="908"/> + <location filename="installationmanager.cpp" line="912"/> <source>failed to open archive</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="911"/> + <location filename="installationmanager.cpp" line="915"/> <source>unsupported archive type</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="914"/> + <location filename="installationmanager.cpp" line="918"/> <source>internal library error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="917"/> + <location filename="installationmanager.cpp" line="921"/> <source>archive invalid</source> <translation type="unfinished"></translation> </message> <message> - <location filename="installationmanager.cpp" line="921"/> + <location filename="installationmanager.cpp" line="925"/> <source>unknown archive error</source> <translation type="unfinished"></translation> </message> @@ -1523,22 +1525,52 @@ This is likely due to a corrupted or incompatible download or unrecognized archi </message> </context> <context> - <name>LogBuffer</name> + <name>LogList</name> + <message> + <location filename="loglist.cpp" line="219"/> + <source>&Copy all</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="loglist.cpp" line="221"/> + <source>C&lear all</source> + <translation type="unfinished"></translation> + </message> <message> - <location filename="logbuffer.cpp" line="86"/> - <source>failed to write log to %1: %2</source> + <location filename="loglist.cpp" line="223"/> + <source>&Level</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="loglist.cpp" line="243"/> + <source>&Debug</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="loglist.cpp" line="244"/> + <source>&Info</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="loglist.cpp" line="245"/> + <source>&Warnings</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="loglist.cpp" line="246"/> + <source>&Errors</source> <translation type="unfinished"></translation> </message> </context> <context> <name>MOApplication</name> <message> - <location filename="moapplication.cpp" line="119"/> + <location filename="moapplication.cpp" line="121"/> <source>an error occurred: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="moapplication.cpp" line="124"/> + <location filename="moapplication.cpp" line="127"/> <source>an error occurred</source> <translation type="unfinished"></translation> </message> @@ -1546,27 +1578,27 @@ This is likely due to a corrupted or incompatible download or unrecognized archi <context> <name>MOBase::TextViewer</name> <message> - <location filename="../../uibase/src/textviewer.cpp" line="58"/> + <location filename="../../uibase/src/textviewer.cpp" line="59"/> <source>Save changes?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/textviewer.cpp" line="59"/> + <location filename="../../uibase/src/textviewer.cpp" line="60"/> <source>Do you want to save changes to %1?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/textviewer.cpp" line="142"/> + <location filename="../../uibase/src/textviewer.cpp" line="143"/> <source>failed to write to %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/textviewer.cpp" line="178"/> + <location filename="../../uibase/src/textviewer.cpp" line="179"/> <source>file not found: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/textviewer.cpp" line="203"/> + <location filename="../../uibase/src/textviewer.cpp" line="204"/> <source>Save</source> <translation type="unfinished"></translation> </message> @@ -1574,7 +1606,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi <context> <name>MOBase::TutorialControl</name> <message> - <location filename="../../uibase/src/tutorialcontrol.cpp" line="146"/> + <location filename="../../uibase/src/tutorialcontrol.cpp" line="148"/> <source>Tutorial failed to start, please check "mo_interface.log" for details.</source> <translation type="unfinished"></translation> </message> @@ -1590,48 +1622,48 @@ This is likely due to a corrupted or incompatible download or unrecognized archi <context> <name>MainWindow</name> <message> - <location filename="mainwindow.ui" line="61"/> - <location filename="mainwindow.ui" line="536"/> + <location filename="mainwindow.ui" line="57"/> + <location filename="mainwindow.ui" line="532"/> <source>Categories</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="133"/> + <location filename="mainwindow.ui" line="129"/> <source>Clear</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="152"/> + <location filename="mainwindow.ui" line="148"/> <source>If checked, only mods that match all selected categories are displayed.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="155"/> + <location filename="mainwindow.ui" line="151"/> <source>And</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="165"/> + <location filename="mainwindow.ui" line="161"/> <source>If checked, all mods that match at least one of the selected categories are displayed.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="168"/> + <location filename="mainwindow.ui" line="164"/> <source>Or</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="207"/> + <location filename="mainwindow.ui" line="203"/> <source>Profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="217"/> + <location filename="mainwindow.ui" line="213"/> <source>Pick a module collection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="220"/> + <location filename="mainwindow.ui" line="216"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1641,84 +1673,84 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="251"/> + <location filename="mainwindow.ui" line="247"/> <source>Open list options...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="254"/> + <location filename="mainwindow.ui" line="250"/> <source>Refresh list. This is usually not necessary unless you modified data outside the program.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="274"/> + <location filename="mainwindow.ui" line="270"/> <source>Show Open Folders menu...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="288"/> - <location filename="mainwindow.ui" line="802"/> + <location filename="mainwindow.ui" line="284"/> + <location filename="mainwindow.ui" line="798"/> <source>Restore Backup...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="302"/> - <location filename="mainwindow.ui" line="822"/> - <location filename="mainwindow.cpp" line="4860"/> + <location filename="mainwindow.ui" line="298"/> + <location filename="mainwindow.ui" line="818"/> + <location filename="mainwindow.cpp" line="4750"/> <source>Create Backup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="316"/> - <location filename="mainwindow.ui" line="836"/> + <location filename="mainwindow.ui" line="312"/> + <location filename="mainwindow.ui" line="832"/> <source>Active:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="329"/> + <location filename="mainwindow.ui" line="325"/> <source>This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="356"/> + <location filename="mainwindow.ui" line="352"/> <source>List of available mods.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="359"/> + <location filename="mainwindow.ui" line="355"/> <source>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="447"/> - <location filename="mainwindow.ui" line="555"/> - <location filename="mainwindow.ui" line="937"/> - <location filename="mainwindow.ui" line="1273"/> + <location filename="mainwindow.ui" line="443"/> + <location filename="mainwindow.ui" line="551"/> + <location filename="mainwindow.ui" line="933"/> + <location filename="mainwindow.ui" line="1269"/> <source>Filter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="504"/> + <location filename="mainwindow.ui" line="500"/> <source>Clear all Filters</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="531"/> + <location filename="mainwindow.ui" line="527"/> <source>No groups</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="541"/> + <location filename="mainwindow.ui" line="537"/> <source>Nexus IDs</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="590"/> + <location filename="mainwindow.ui" line="586"/> <source>Pick a program to run.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="593"/> + <location filename="mainwindow.ui" line="589"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1728,12 +1760,12 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="641"/> + <location filename="mainwindow.ui" line="637"/> <source>Run program</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="644"/> + <location filename="mainwindow.ui" line="640"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1742,17 +1774,17 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="654"/> + <location filename="mainwindow.ui" line="650"/> <source>Run</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="695"/> + <location filename="mainwindow.ui" line="691"/> <source>Create a shortcut in your start menu or on the desktop to the specified program</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="698"/> + <location filename="mainwindow.ui" line="694"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1761,32 +1793,32 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="705"/> + <location filename="mainwindow.ui" line="701"/> <source>Shortcut</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="755"/> + <location filename="mainwindow.ui" line="751"/> <source>Plugins</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="778"/> + <location filename="mainwindow.ui" line="774"/> <source>Sort</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="849"/> + <location filename="mainwindow.ui" line="845"/> <source>This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="876"/> + <location filename="mainwindow.ui" line="872"/> <source>List of available esp/esm files</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="879"/> + <location filename="mainwindow.ui" line="875"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1795,27 +1827,27 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="950"/> + <location filename="mainwindow.ui" line="946"/> <source>Archives</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="970"/> + <location filename="mainwindow.ui" line="966"/> <source><html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="973"/> + <location filename="mainwindow.ui" line="969"/> <source><html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="988"/> + <location filename="mainwindow.ui" line="984"/> <source>List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="991"/> + <location filename="mainwindow.ui" line="987"/> <source>BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1823,72 +1855,72 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1020"/> + <location filename="mainwindow.ui" line="1016"/> <source>Data</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1038"/> + <location filename="mainwindow.ui" line="1034"/> <source>refresh data-directory overview</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1041"/> + <location filename="mainwindow.ui" line="1037"/> <source>Refresh the overview. This may take a moment.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1044"/> - <location filename="mainwindow.ui" line="1187"/> - <location filename="mainwindow.cpp" line="4733"/> - <location filename="mainwindow.cpp" line="5551"/> + <location filename="mainwindow.ui" line="1040"/> + <location filename="mainwindow.ui" line="1183"/> + <location filename="mainwindow.cpp" line="4623"/> + <location filename="mainwindow.cpp" line="5442"/> <source>Refresh</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1060"/> + <location filename="mainwindow.ui" line="1056"/> <source>This is an overview of your data directory as visible to the game (and tools). </source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1073"/> + <location filename="mainwindow.ui" line="1069"/> <source>File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1078"/> + <location filename="mainwindow.ui" line="1074"/> <source>Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1090"/> - <location filename="mainwindow.ui" line="1093"/> + <location filename="mainwindow.ui" line="1086"/> + <location filename="mainwindow.ui" line="1089"/> <source>Filters the above list so that only conflicts are displayed.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1096"/> + <location filename="mainwindow.ui" line="1092"/> <source>Show only conflicts</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1103"/> - <location filename="mainwindow.ui" line="1109"/> + <location filename="mainwindow.ui" line="1099"/> + <location filename="mainwindow.ui" line="1105"/> <source>Filters the above list so that files from archives are not shown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1112"/> + <location filename="mainwindow.ui" line="1108"/> <source>Show files from Archives</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1122"/> + <location filename="mainwindow.ui" line="1118"/> <source>Saves</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1146"/> + <location filename="mainwindow.ui" line="1142"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1899,1181 +1931,1170 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1166"/> + <location filename="mainwindow.ui" line="1162"/> <source>Downloads</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1184"/> + <location filename="mainwindow.ui" line="1180"/> <source>Refresh downloads view</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1215"/> + <location filename="mainwindow.ui" line="1211"/> <source>This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1253"/> + <location filename="mainwindow.ui" line="1249"/> <source>Show Hidden</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1315"/> + <location filename="mainwindow.ui" line="1293"/> <source>Main ToolBar</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1357"/> + <location filename="mainwindow.ui" line="1335"/> <source>&File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1367"/> - <location filename="mainwindow.ui" line="1492"/> + <location filename="mainwindow.ui" line="1345"/> + <location filename="mainwindow.ui" line="1514"/> <source>&Tools</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1378"/> - <location filename="mainwindow.ui" line="1588"/> - <location filename="mainwindow.ui" line="1591"/> + <location filename="mainwindow.ui" line="1356"/> + <location filename="mainwindow.ui" line="1610"/> + <location filename="mainwindow.ui" line="1613"/> <source>&Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1387"/> + <location filename="mainwindow.ui" line="1365"/> <source>&View</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1391"/> + <location filename="mainwindow.ui" line="1369"/> <source>&Toolbars</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1411"/> + <location filename="mainwindow.ui" line="1390"/> <source>&Run</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1426"/> + <location filename="mainwindow.ui" line="1404"/> + <location filename="mainwindow.ui" line="1752"/> + <source>Log</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="mainwindow.ui" line="1448"/> <source>Install &Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1429"/> + <location filename="mainwindow.ui" line="1451"/> <source>Install &Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1432"/> - <location filename="mainwindow.ui" line="1435"/> + <location filename="mainwindow.ui" line="1454"/> + <location filename="mainwindow.ui" line="1457"/> <source>Install a new mod from an archive</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1438"/> + <location filename="mainwindow.ui" line="1460"/> <source>Ctrl+M</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1447"/> + <location filename="mainwindow.ui" line="1469"/> <source>&Profiles...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1450"/> + <location filename="mainwindow.ui" line="1472"/> <source>&Profiles</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1453"/> - <location filename="mainwindow.ui" line="1456"/> + <location filename="mainwindow.ui" line="1475"/> + <location filename="mainwindow.ui" line="1478"/> <source>Configure profiles</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1459"/> + <location filename="mainwindow.ui" line="1481"/> <source>Ctrl+P</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1468"/> + <location filename="mainwindow.ui" line="1490"/> <source>&Executables...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1471"/> + <location filename="mainwindow.ui" line="1493"/> <source>&Executables</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1474"/> - <location filename="mainwindow.ui" line="1477"/> + <location filename="mainwindow.ui" line="1496"/> + <location filename="mainwindow.ui" line="1499"/> <source>Configure the executables that can be started through Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1480"/> + <location filename="mainwindow.ui" line="1502"/> <source>Ctrl+E</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1489"/> + <location filename="mainwindow.ui" line="1511"/> <source>&Tool Plugins</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1495"/> + <location filename="mainwindow.ui" line="1517"/> <source>Tools</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1498"/> + <location filename="mainwindow.ui" line="1520"/> <source>Ctrl+I</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1507"/> + <location filename="mainwindow.ui" line="1529"/> <source>&Settings...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1510"/> + <location filename="mainwindow.ui" line="1532"/> <source>&Settings</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1513"/> - <location filename="mainwindow.ui" line="1516"/> + <location filename="mainwindow.ui" line="1535"/> + <location filename="mainwindow.ui" line="1538"/> <source>Configure settings and workarounds</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1519"/> + <location filename="mainwindow.ui" line="1541"/> <source>Ctrl+S</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1528"/> - <location filename="mainwindow.ui" line="1531"/> + <location filename="mainwindow.ui" line="1550"/> + <location filename="mainwindow.ui" line="1553"/> <source>Visit &Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1534"/> - <location filename="mainwindow.ui" line="1537"/> + <location filename="mainwindow.ui" line="1556"/> + <location filename="mainwindow.ui" line="1559"/> <source>Visit the Nexus website in your browser for more mods</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1540"/> + <location filename="mainwindow.ui" line="1562"/> <source>Ctrl+N</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1552"/> - <location filename="mainwindow.ui" line="1555"/> + <location filename="mainwindow.ui" line="1574"/> + <location filename="mainwindow.ui" line="1577"/> <source>&Update Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1558"/> - <location filename="mainwindow.ui" line="1561"/> + <location filename="mainwindow.ui" line="1580"/> + <location filename="mainwindow.ui" line="1583"/> <source>Mod Organizer is up-to-date</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1570"/> + <location filename="mainwindow.ui" line="1592"/> <source>&Notifications...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1573"/> - <location filename="mainwindow.ui" line="1576"/> + <location filename="mainwindow.ui" line="1595"/> + <location filename="mainwindow.ui" line="1598"/> <source>Open the notifications dialog</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1579"/> + <location filename="mainwindow.ui" line="1601"/> <source>This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1594"/> - <location filename="mainwindow.ui" line="1597"/> + <location filename="mainwindow.ui" line="1616"/> + <location filename="mainwindow.ui" line="1619"/> <source>Show help options</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1600"/> + <location filename="mainwindow.ui" line="1622"/> <source>Ctrl+H</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1609"/> - <location filename="mainwindow.ui" line="1612"/> + <location filename="mainwindow.ui" line="1631"/> + <location filename="mainwindow.ui" line="1634"/> <source>&Endorse ModOrganizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1615"/> - <location filename="mainwindow.ui" line="1618"/> - <location filename="mainwindow.cpp" line="5579"/> + <location filename="mainwindow.ui" line="1637"/> + <location filename="mainwindow.ui" line="1640"/> + <location filename="mainwindow.cpp" line="5470"/> <source>Endorse Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1623"/> - <location filename="mainwindow.ui" line="1626"/> - <source>Copy &Log</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1629"/> - <location filename="mainwindow.ui" line="1632"/> - <source>Copy log to clipboard</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1641"/> + <location filename="mainwindow.ui" line="1649"/> <source>&Change Game...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1644"/> + <location filename="mainwindow.ui" line="1652"/> <source>&Change Game</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1647"/> - <location filename="mainwindow.ui" line="1650"/> + <location filename="mainwindow.ui" line="1655"/> + <location filename="mainwindow.ui" line="1658"/> <source>Open the Instance selection dialog to manage a different Game</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1655"/> - <location filename="mainwindow.ui" line="1658"/> + <location filename="mainwindow.ui" line="1663"/> + <location filename="mainwindow.ui" line="1666"/> <source>E&xit</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1661"/> - <location filename="mainwindow.ui" line="1664"/> + <location filename="mainwindow.ui" line="1669"/> + <location filename="mainwindow.ui" line="1672"/> <source>Exits Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1672"/> + <location filename="mainwindow.ui" line="1680"/> <source>M&ain Toolbar</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1680"/> + <location filename="mainwindow.ui" line="1688"/> <source>&Small Icons</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1688"/> + <location filename="mainwindow.ui" line="1696"/> <source>Lar&ge Icons</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1696"/> + <location filename="mainwindow.ui" line="1704"/> <source>&Icons Only</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1704"/> + <location filename="mainwindow.ui" line="1712"/> <source>&Text Only</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1712"/> + <location filename="mainwindow.ui" line="1720"/> <source>I&cons and Text</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1720"/> + <location filename="mainwindow.ui" line="1728"/> <source>M&edium Icons</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1728"/> + <location filename="mainwindow.ui" line="1736"/> <source>&Menu</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.ui" line="1736"/> + <location filename="mainwindow.ui" line="1744"/> <source>Status &bar</source> <oldsource>St&atus bar</oldsource> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="341"/> + <location filename="mainwindow.cpp" line="295"/> <source>Toolbar and Menu</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="342"/> + <location filename="mainwindow.cpp" line="296"/> <source>Desktop</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="343"/> + <location filename="mainwindow.cpp" line="297"/> <source>Start Menu</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="368"/> + <location filename="mainwindow.cpp" line="322"/> <source>There is no supported sort mechanism for this game. You will probably have to use a third-party tool.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="578"/> + <location filename="mainwindow.cpp" line="601"/> <source>Crash on exit</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="579"/> + <location filename="mainwindow.cpp" line="602"/> <source>MO crashed while exiting. Some settings may not be saved. Error: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="929"/> + <location filename="mainwindow.cpp" line="928"/> <source>There are notifications to read</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="948"/> + <location filename="mainwindow.cpp" line="947"/> <source>There are no notifications</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1035"/> - <location filename="mainwindow.cpp" line="4870"/> - <location filename="mainwindow.cpp" line="4874"/> + <location filename="mainwindow.cpp" line="1034"/> + <location filename="mainwindow.cpp" line="4760"/> + <location filename="mainwindow.cpp" line="4764"/> <source>Endorse</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1039"/> + <location filename="mainwindow.cpp" line="1038"/> <source>Won't Endorse</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1056"/> + <location filename="mainwindow.cpp" line="1055"/> <source>Help on UI</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1060"/> + <location filename="mainwindow.cpp" line="1059"/> <source>Documentation</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1064"/> + <location filename="mainwindow.cpp" line="1063"/> <source>Chat on Discord</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1068"/> + <location filename="mainwindow.cpp" line="1067"/> <source>Report Issue</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1072"/> + <location filename="mainwindow.cpp" line="1071"/> <source>Tutorials</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1111"/> + <location filename="mainwindow.cpp" line="1110"/> <source>About</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1112"/> + <location filename="mainwindow.cpp" line="1111"/> <source>About Qt</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1171"/> + <location filename="mainwindow.cpp" line="1170"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1172"/> + <location filename="mainwindow.cpp" line="1171"/> <source>Please enter a name for the new profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1180"/> + <location filename="mainwindow.cpp" line="1179"/> <source>failed to create profile: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1234"/> + <location filename="mainwindow.cpp" line="1235"/> <source>Show tutorial?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1235"/> + <location filename="mainwindow.cpp" line="1236"/> <source>You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1278"/> + <location filename="mainwindow.cpp" line="1280"/> <source>Downloads in progress</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1279"/> + <location filename="mainwindow.cpp" line="1281"/> <source>There are still downloads in progress, do you really want to quit?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1403"/> + <location filename="mainwindow.cpp" line="1399"/> <source>Plugin "%1" failed: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1405"/> + <location filename="mainwindow.cpp" line="1401"/> <source>Plugin "%1" failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1485"/> + <location filename="mainwindow.cpp" line="1481"/> <source>Browse Mod Page</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1713"/> + <location filename="mainwindow.cpp" line="1695"/> <source>Also in: <br></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1724"/> + <location filename="mainwindow.cpp" line="1706"/> <source>No conflict</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1809"/> + <location filename="mainwindow.cpp" line="1792"/> <source><Edit...></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2087"/> + <location filename="mainwindow.cpp" line="2070"/> <source>This bsa is enabled in the ini file so it may be required!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2149"/> + <location filename="mainwindow.cpp" line="2123"/> <source>Activating Network Proxy</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2252"/> + <location filename="mainwindow.cpp" line="2200"/> <source>Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2365"/> + <location filename="mainwindow.cpp" line="2302"/> <source>Choose Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2366"/> + <location filename="mainwindow.cpp" line="2303"/> <source>Mod Archive</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2473"/> + <location filename="mainwindow.cpp" line="2399"/> <source>Start Tutorial?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2474"/> + <location filename="mainwindow.cpp" line="2400"/> <source>You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2648"/> - <source>failed to spawn notepad.exe: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2688"/> + <location filename="mainwindow.cpp" line="2599"/> <source>failed to change origin name: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2712"/> + <location filename="mainwindow.cpp" line="2623"/> <source>failed to move "%1" from mod "%2" to "%3": %4</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2736"/> + <location filename="mainwindow.cpp" line="2647"/> <source><Contains %1></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2771"/> + <location filename="mainwindow.cpp" line="2682"/> <source><Checked></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2772"/> + <location filename="mainwindow.cpp" line="2683"/> <source><Unchecked></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2773"/> + <location filename="mainwindow.cpp" line="2684"/> <source><Update></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2774"/> + <location filename="mainwindow.cpp" line="2685"/> <source><Mod Backup></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2775"/> + <location filename="mainwindow.cpp" line="2686"/> <source><Managed by MO></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2776"/> + <location filename="mainwindow.cpp" line="2687"/> <source><Managed outside MO></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2777"/> + <location filename="mainwindow.cpp" line="2688"/> <source><No category></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2778"/> + <location filename="mainwindow.cpp" line="2689"/> <source><Conflicted></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2779"/> + <location filename="mainwindow.cpp" line="2690"/> <source><Not Endorsed></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2825"/> + <location filename="mainwindow.cpp" line="2736"/> <source>failed to rename mod: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2838"/> + <location filename="mainwindow.cpp" line="2749"/> <source>Overwrite?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2839"/> + <location filename="mainwindow.cpp" line="2750"/> <source>This will replace the existing mod "%1". Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2842"/> + <location filename="mainwindow.cpp" line="2753"/> <source>failed to remove mod "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2846"/> - <location filename="mainwindow.cpp" line="5379"/> - <location filename="mainwindow.cpp" line="5403"/> + <location filename="mainwindow.cpp" line="2757"/> + <location filename="mainwindow.cpp" line="5270"/> + <location filename="mainwindow.cpp" line="5294"/> <source>failed to rename "%1" to "%2"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2934"/> - <location filename="mainwindow.cpp" line="4437"/> - <location filename="mainwindow.cpp" line="4445"/> - <location filename="mainwindow.cpp" line="5004"/> + <location filename="mainwindow.cpp" line="2845"/> + <location filename="mainwindow.cpp" line="4327"/> + <location filename="mainwindow.cpp" line="4335"/> + <location filename="mainwindow.cpp" line="4894"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2935"/> + <location filename="mainwindow.cpp" line="2846"/> <source>Remove the following mods?<br><ul>%1</ul></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2950"/> + <location filename="mainwindow.cpp" line="2861"/> <source>failed to remove mod: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2982"/> - <location filename="mainwindow.cpp" line="2985"/> - <location filename="mainwindow.cpp" line="2995"/> + <location filename="mainwindow.cpp" line="2893"/> + <location filename="mainwindow.cpp" line="2896"/> + <location filename="mainwindow.cpp" line="2906"/> <source>Failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2982"/> + <location filename="mainwindow.cpp" line="2893"/> <source>Installation file no longer exists</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2986"/> + <location filename="mainwindow.cpp" line="2897"/> <source>Mods installed with old versions of MO can't be reinstalled in this way.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="2996"/> + <location filename="mainwindow.cpp" line="2907"/> <source>Failed to create backup.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3024"/> + <location filename="mainwindow.cpp" line="2935"/> <source>Endorsing multiple mods will take a while. Please wait...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3060"/> + <location filename="mainwindow.cpp" line="2971"/> <source>Unendorsing multiple mods will take a while. Please wait...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3149"/> + <location filename="mainwindow.cpp" line="3048"/> <source>Failed to display overwrite dialog: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3349"/> + <location filename="mainwindow.cpp" line="3239"/> <source>Opening Nexus Links</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3350"/> + <location filename="mainwindow.cpp" line="3240"/> <source>You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3377"/> + <location filename="mainwindow.cpp" line="3267"/> <source>Nexus ID for this mod is unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3388"/> + <location filename="mainwindow.cpp" line="3278"/> <source>Opening Web Pages</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3389"/> + <location filename="mainwindow.cpp" line="3279"/> <source>You are trying to open %1 Web Pages. Are you sure you want to do this?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3595"/> + <location filename="mainwindow.cpp" line="3485"/> <source><table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3650"/> + <location filename="mainwindow.cpp" line="3540"/> <source><table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3682"/> - <location filename="mainwindow.cpp" line="3820"/> - <location filename="mainwindow.cpp" line="4795"/> + <location filename="mainwindow.cpp" line="3572"/> + <location filename="mainwindow.cpp" line="3716"/> + <location filename="mainwindow.cpp" line="4685"/> <source>Create Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3683"/> + <location filename="mainwindow.cpp" line="3573"/> <source>This will create an empty mod. Please enter a name:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3692"/> - <location filename="mainwindow.cpp" line="3830"/> + <location filename="mainwindow.cpp" line="3582"/> + <location filename="mainwindow.cpp" line="3726"/> <source>A mod with this name already exists</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3720"/> + <location filename="mainwindow.cpp" line="3610"/> <source>Create Separator...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3721"/> + <location filename="mainwindow.cpp" line="3611"/> <source>This will create a new separator. Please enter a name:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3728"/> + <location filename="mainwindow.cpp" line="3618"/> <source>A separator with this name already exists</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3821"/> + <location filename="mainwindow.cpp" line="3717"/> <source>This will move all files from overwrite into a new, regular mod. Please enter a name:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3902"/> + <location filename="mainwindow.cpp" line="3790"/> <source>Move successful.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3923"/> - <location filename="mainwindow.cpp" line="6141"/> + <location filename="mainwindow.cpp" line="3812"/> + <location filename="mainwindow.cpp" line="6094"/> <source>Are you sure?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="3924"/> + <location filename="mainwindow.cpp" line="3813"/> <source>About to recursively delete: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4319"/> + <location filename="mainwindow.cpp" line="4209"/> <source>Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4320"/> + <location filename="mainwindow.cpp" line="4210"/> <source>The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4340"/> + <location filename="mainwindow.cpp" line="4230"/> <source>Sorry</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4341"/> + <location filename="mainwindow.cpp" line="4231"/> <source>I don't know a versioning scheme where %1 is newer than %2.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4437"/> + <location filename="mainwindow.cpp" line="4327"/> <source>Really enable all visible mods?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4445"/> + <location filename="mainwindow.cpp" line="4335"/> <source>Really disable all visible mods?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4522"/> + <location filename="mainwindow.cpp" line="4412"/> <source>Export to csv</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4525"/> + <location filename="mainwindow.cpp" line="4415"/> <source>CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4528"/> + <location filename="mainwindow.cpp" line="4418"/> <source>Select what mods you want export:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4529"/> + <location filename="mainwindow.cpp" line="4419"/> <source>All installed mods</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4530"/> + <location filename="mainwindow.cpp" line="4420"/> <source>Only active (checked) mods from your current profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4531"/> + <location filename="mainwindow.cpp" line="4421"/> <source>All currently visible mods in the mod list</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4552"/> + <location filename="mainwindow.cpp" line="4442"/> <source>Choose what Columns to export:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4555"/> + <location filename="mainwindow.cpp" line="4445"/> <source>Mod_Priority</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4557"/> + <location filename="mainwindow.cpp" line="4447"/> <source>Mod_Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4559"/> + <location filename="mainwindow.cpp" line="4449"/> <source>Notes_column</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4560"/> + <location filename="mainwindow.cpp" line="4450"/> <source>Mod_Status</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4562"/> + <location filename="mainwindow.cpp" line="4452"/> <source>Primary_Category</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4563"/> + <location filename="mainwindow.cpp" line="4453"/> <source>Nexus_ID</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4564"/> + <location filename="mainwindow.cpp" line="4454"/> <source>Mod_Nexus_URL</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4565"/> + <location filename="mainwindow.cpp" line="4455"/> <source>Mod_Version</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4566"/> + <location filename="mainwindow.cpp" line="4456"/> <source>Install_Date</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4567"/> + <location filename="mainwindow.cpp" line="4457"/> <source>Download_File_Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4675"/> + <location filename="mainwindow.cpp" line="4565"/> <source>export failed: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4694"/> + <location filename="mainwindow.cpp" line="4584"/> <source>Open Game folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4696"/> + <location filename="mainwindow.cpp" line="4586"/> <source>Open MyGames folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4698"/> + <location filename="mainwindow.cpp" line="4588"/> <source>Open INIs folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4702"/> + <location filename="mainwindow.cpp" line="4592"/> <source>Open Instance folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4704"/> + <location filename="mainwindow.cpp" line="4594"/> <source>Open Mods folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4706"/> + <location filename="mainwindow.cpp" line="4596"/> <source>Open Profile folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4708"/> + <location filename="mainwindow.cpp" line="4598"/> <source>Open Downloads folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4712"/> + <location filename="mainwindow.cpp" line="4602"/> <source>Open MO2 Install folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4714"/> + <location filename="mainwindow.cpp" line="4604"/> <source>Open MO2 Plugins folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4716"/> + <location filename="mainwindow.cpp" line="4606"/> <source>Open MO2 Logs folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4724"/> + <location filename="mainwindow.cpp" line="4614"/> <source>Install Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4725"/> + <location filename="mainwindow.cpp" line="4615"/> <source>Create empty mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4726"/> + <location filename="mainwindow.cpp" line="4616"/> <source>Create Separator</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4730"/> + <location filename="mainwindow.cpp" line="4620"/> <source>Enable all visible</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4731"/> + <location filename="mainwindow.cpp" line="4621"/> <source>Disable all visible</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4732"/> + <location filename="mainwindow.cpp" line="4622"/> <source>Check for updates</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4734"/> + <location filename="mainwindow.cpp" line="4624"/> <source>Export to csv...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4743"/> - <location filename="mainwindow.cpp" line="4759"/> + <location filename="mainwindow.cpp" line="4633"/> + <location filename="mainwindow.cpp" line="4649"/> <source>Send to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4744"/> - <location filename="mainwindow.cpp" line="4760"/> + <location filename="mainwindow.cpp" line="4634"/> + <location filename="mainwindow.cpp" line="4650"/> <source>Top</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4745"/> - <location filename="mainwindow.cpp" line="4761"/> + <location filename="mainwindow.cpp" line="4635"/> + <location filename="mainwindow.cpp" line="4651"/> <source>Bottom</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4746"/> - <location filename="mainwindow.cpp" line="4762"/> + <location filename="mainwindow.cpp" line="4636"/> + <location filename="mainwindow.cpp" line="4652"/> <source>Priority...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4747"/> + <location filename="mainwindow.cpp" line="4637"/> <source>Separator...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4786"/> + <location filename="mainwindow.cpp" line="4676"/> <source>All Mods</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4794"/> + <location filename="mainwindow.cpp" line="4684"/> <source>Sync to Mods...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4796"/> + <location filename="mainwindow.cpp" line="4686"/> <source>Move content to Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4797"/> + <location filename="mainwindow.cpp" line="4687"/> <source>Clear Overwrite...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4799"/> - <location filename="mainwindow.cpp" line="4922"/> + <location filename="mainwindow.cpp" line="4689"/> + <location filename="mainwindow.cpp" line="4812"/> <source>Open in Explorer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4801"/> + <location filename="mainwindow.cpp" line="4691"/> <source>Restore Backup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4802"/> + <location filename="mainwindow.cpp" line="4692"/> <source>Remove Backup...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4805"/> - <location filename="mainwindow.cpp" line="4824"/> + <location filename="mainwindow.cpp" line="4695"/> + <location filename="mainwindow.cpp" line="4714"/> <source>Change Categories</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4809"/> - <location filename="mainwindow.cpp" line="4829"/> + <location filename="mainwindow.cpp" line="4699"/> + <location filename="mainwindow.cpp" line="4719"/> <source>Primary Category</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4813"/> + <location filename="mainwindow.cpp" line="4703"/> <source>Rename Separator...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4814"/> + <location filename="mainwindow.cpp" line="4704"/> <source>Remove Separator...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4817"/> + <location filename="mainwindow.cpp" line="4707"/> <source>Select Color...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4819"/> + <location filename="mainwindow.cpp" line="4709"/> <source>Reset Color</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4836"/> + <location filename="mainwindow.cpp" line="4726"/> <source>Change versioning scheme</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4840"/> + <location filename="mainwindow.cpp" line="4730"/> <source>Force-check updates</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4842"/> + <location filename="mainwindow.cpp" line="4732"/> <source>Un-ignore update</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4845"/> + <location filename="mainwindow.cpp" line="4735"/> <source>Ignore update</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4850"/> - <location filename="mainwindow.cpp" line="6265"/> + <location filename="mainwindow.cpp" line="4740"/> + <location filename="mainwindow.cpp" line="6210"/> <source>Enable selected</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4851"/> - <location filename="mainwindow.cpp" line="6266"/> + <location filename="mainwindow.cpp" line="4741"/> + <location filename="mainwindow.cpp" line="6211"/> <source>Disable selected</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4857"/> + <location filename="mainwindow.cpp" line="4747"/> <source>Rename Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4858"/> + <location filename="mainwindow.cpp" line="4748"/> <source>Reinstall Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4859"/> + <location filename="mainwindow.cpp" line="4749"/> <source>Remove Mod...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4867"/> + <location filename="mainwindow.cpp" line="4757"/> <source>Un-Endorse</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4871"/> + <location filename="mainwindow.cpp" line="4761"/> <source>Won't endorse</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4877"/> + <location filename="mainwindow.cpp" line="4767"/> <source>Endorsement state unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4887"/> + <location filename="mainwindow.cpp" line="4777"/> <source>Start tracking</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4890"/> + <location filename="mainwindow.cpp" line="4780"/> <source>Stop tracking</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4893"/> + <location filename="mainwindow.cpp" line="4783"/> <source>Tracked state unknown</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4904"/> + <location filename="mainwindow.cpp" line="4794"/> <source>Ignore missing data</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4908"/> + <location filename="mainwindow.cpp" line="4798"/> <source>Mark as converted/working</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4912"/> + <location filename="mainwindow.cpp" line="4802"/> <source>Visit on Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4918"/> + <location filename="mainwindow.cpp" line="4808"/> <source>Visit on %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4926"/> + <location filename="mainwindow.cpp" line="4816"/> <source>Information...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4933"/> - <location filename="mainwindow.cpp" line="6318"/> + <location filename="mainwindow.cpp" line="4823"/> + <location filename="mainwindow.cpp" line="6263"/> <source>Exception: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4935"/> - <location filename="mainwindow.cpp" line="6320"/> + <location filename="mainwindow.cpp" line="4825"/> + <location filename="mainwindow.cpp" line="6265"/> <source>Unknown exception</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4964"/> + <location filename="mainwindow.cpp" line="4854"/> <source><All></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="4966"/> + <location filename="mainwindow.cpp" line="4856"/> <source><Multiple></source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5001"/> + <location filename="mainwindow.cpp" line="4891"/> <source>%1 more</source> <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location filename="mainwindow.cpp" line="5005"/> + <location filename="mainwindow.cpp" line="4895"/> <source>Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin.</source> <translation type="unfinished"> <numerusform></numerusform> @@ -3081,12 +3102,12 @@ You can also use online editors and converters instead.</source> </translation> </message> <message> - <location filename="mainwindow.cpp" line="5050"/> + <location filename="mainwindow.cpp" line="4940"/> <source>Enable Mods...</source> <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location filename="mainwindow.cpp" line="5065"/> + <location filename="mainwindow.cpp" line="4955"/> <source>Delete %n save(s)</source> <translation type="unfinished"> <numerusform></numerusform> @@ -3094,12 +3115,12 @@ You can also use online editors and converters instead.</source> </translation> </message> <message> - <location filename="mainwindow.cpp" line="5124"/> + <location filename="mainwindow.cpp" line="5016"/> <source>Restarting MO</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5125"/> + <location filename="mainwindow.cpp" line="5017"/> <source>Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3107,348 +3128,336 @@ Click OK to restart MO now.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5145"/> + <location filename="mainwindow.cpp" line="5037"/> <source>Can't change download directory while downloads are in progress!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5292"/> + <location filename="mainwindow.cpp" line="5183"/> <source>failed to write to file %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5298"/> + <location filename="mainwindow.cpp" line="5189"/> <source>%1 written</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5322"/> + <location filename="mainwindow.cpp" line="5213"/> <source>Enter Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5323"/> + <location filename="mainwindow.cpp" line="5214"/> <source>Please enter a name for the executable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5342"/> + <location filename="mainwindow.cpp" line="5233"/> <source>Not an executable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5342"/> + <location filename="mainwindow.cpp" line="5233"/> <source>This is not a recognized executable.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5364"/> - <location filename="mainwindow.cpp" line="5389"/> + <location filename="mainwindow.cpp" line="5255"/> + <location filename="mainwindow.cpp" line="5280"/> <source>Replace file?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5364"/> + <location filename="mainwindow.cpp" line="5255"/> <source>There already is a hidden version of this file. Replace it?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5367"/> - <location filename="mainwindow.cpp" line="5392"/> + <location filename="mainwindow.cpp" line="5258"/> + <location filename="mainwindow.cpp" line="5283"/> <source>File operation failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5367"/> - <location filename="mainwindow.cpp" line="5392"/> + <location filename="mainwindow.cpp" line="5258"/> + <location filename="mainwindow.cpp" line="5283"/> <source>Failed to remove "%1". Maybe you lack the required file permissions?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5389"/> + <location filename="mainwindow.cpp" line="5280"/> <source>There already is a visible version of this file. Replace it?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5433"/> - <location filename="mainwindow.cpp" line="6928"/> + <location filename="mainwindow.cpp" line="5324"/> + <location filename="mainwindow.cpp" line="6836"/> <source>Set Priority</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5433"/> + <location filename="mainwindow.cpp" line="5324"/> <source>Set the priority of the selected plugins</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5497"/> + <location filename="mainwindow.cpp" line="5388"/> <source>Update available</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5524"/> + <location filename="mainwindow.cpp" line="5415"/> <source>Open/Execute</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5525"/> + <location filename="mainwindow.cpp" line="5416"/> <source>Add as Executable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5529"/> + <location filename="mainwindow.cpp" line="5420"/> <source>Preview</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5542"/> + <location filename="mainwindow.cpp" line="5433"/> <source>Un-Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5544"/> + <location filename="mainwindow.cpp" line="5435"/> <source>Hide</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5550"/> + <location filename="mainwindow.cpp" line="5441"/> <source>Write To File...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5580"/> + <location filename="mainwindow.cpp" line="5471"/> <source>Do you want to endorse Mod Organizer on %1 now?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5594"/> + <location filename="mainwindow.cpp" line="5485"/> <source>Abstain from Endorsing Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5595"/> + <location filename="mainwindow.cpp" line="5486"/> <source>Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5675"/> - <location filename="mainwindow.cpp" line="5676"/> + <location filename="mainwindow.cpp" line="5577"/> <source>Thank you for endorsing MO2! :)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5678"/> - <location filename="mainwindow.cpp" line="5679"/> + <location filename="mainwindow.cpp" line="5583"/> <source>Please reconsider endorsing MO2 on Nexus!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5914"/> + <location filename="mainwindow.cpp" line="5841"/> <source>Thank you!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5914"/> + <location filename="mainwindow.cpp" line="5841"/> <source>Thank you for your endorsement!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5917"/> - <source>Okay.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5917"/> - <source>This mod will not be endorsed and will no longer ask you to endorse.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5976"/> + <location filename="mainwindow.cpp" line="5933"/> <source>Mod ID %1 no longer seems to be available on Nexus.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5978"/> + <location filename="mainwindow.cpp" line="5935"/> <source>Request to Nexus failed: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="5994"/> - <location filename="mainwindow.cpp" line="6056"/> + <location filename="mainwindow.cpp" line="5951"/> + <location filename="mainwindow.cpp" line="6013"/> <source>failed to read %1: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6006"/> - <location filename="mainwindow.cpp" line="6493"/> + <location filename="mainwindow.cpp" line="5963"/> + <location filename="mainwindow.cpp" line="6437"/> <source>Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6006"/> + <location filename="mainwindow.cpp" line="5963"/> <source>failed to extract %1 (errorcode %2)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6038"/> + <location filename="mainwindow.cpp" line="5995"/> <source>Extract BSA</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6067"/> + <location filename="mainwindow.cpp" line="6024"/> <source>This archive contains invalid hashes. Some files may be broken.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6113"/> + <location filename="mainwindow.cpp" line="6070"/> <source>Extract...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6142"/> + <location filename="mainwindow.cpp" line="6094"/> <source>This will restart MO, continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6189"/> + <location filename="mainwindow.cpp" line="6136"/> <source>Edit Categories...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6190"/> + <location filename="mainwindow.cpp" line="6137"/> <source>Deselect filter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6249"/> + <location filename="mainwindow.cpp" line="6194"/> <source>Remove '%1' from the toolbar</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6270"/> + <location filename="mainwindow.cpp" line="6215"/> <source>Enable all</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6271"/> + <location filename="mainwindow.cpp" line="6216"/> <source>Disable all</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6292"/> + <location filename="mainwindow.cpp" line="6237"/> <source>Unlock load order</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6295"/> + <location filename="mainwindow.cpp" line="6240"/> <source>Lock load order</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6305"/> + <location filename="mainwindow.cpp" line="6250"/> <source>Open Origin in Explorer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6310"/> + <location filename="mainwindow.cpp" line="6255"/> <source>Open Origin Info...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6439"/> + <location filename="mainwindow.cpp" line="6384"/> <source>depends on missing "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6443"/> + <location filename="mainwindow.cpp" line="6388"/> <source>incompatible with "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6469"/> + <location filename="mainwindow.cpp" line="6413"/> <source>Please wait while LOOT is running</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6566"/> + <location filename="mainwindow.cpp" line="6512"/> <source>loot failed. Exit code was: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6588"/> + <location filename="mainwindow.cpp" line="6534"/> <source>failed to start loot</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6591"/> + <location filename="mainwindow.cpp" line="6537"/> <source>failed to run loot: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6595"/> + <location filename="mainwindow.cpp" line="6541"/> <source>Errors occurred</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6642"/> + <location filename="mainwindow.cpp" line="6578"/> <source>Backup of load order created</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6652"/> + <location filename="mainwindow.cpp" line="6588"/> <source>Choose backup to restore</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6665"/> + <location filename="mainwindow.cpp" line="6601"/> <source>No Backups</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6665"/> + <location filename="mainwindow.cpp" line="6601"/> <source>There are no backups to restore</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6686"/> - <location filename="mainwindow.cpp" line="6708"/> + <location filename="mainwindow.cpp" line="6626"/> + <location filename="mainwindow.cpp" line="6651"/> <source>Restore failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6687"/> - <location filename="mainwindow.cpp" line="6709"/> + <location filename="mainwindow.cpp" line="6627"/> + <location filename="mainwindow.cpp" line="6652"/> <source>Failed to restore the backup. Errorcode: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6698"/> + <location filename="mainwindow.cpp" line="6639"/> <source>Backup of mod list created</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6804"/> + <location filename="mainwindow.cpp" line="6736"/> <source>A file with the same name has already been downloaded. What would you like to do?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6806"/> + <location filename="mainwindow.cpp" line="6738"/> <source>Overwrite</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6807"/> + <location filename="mainwindow.cpp" line="6739"/> <source>Rename new file</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6808"/> + <location filename="mainwindow.cpp" line="6740"/> <source>Ignore file</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="6928"/> + <location filename="mainwindow.cpp" line="6836"/> <source>Set the priority of the selected mods</source> <translation type="unfinished"></translation> </message> @@ -3940,12 +3949,12 @@ p, li { white-space: pre-wrap; } <context> <name>ModInfoRegular</name> <message> - <location filename="modinforegular.cpp" line="731"/> + <location filename="modinforegular.cpp" line="729"/> <source>%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinforegular.cpp" line="735"/> + <location filename="modinforegular.cpp" line="733"/> <source>Categories: <br></source> <translation type="unfinished"></translation> </message> @@ -4280,7 +4289,7 @@ p, li { white-space: pre-wrap; } <context> <name>ModListSortProxy</name> <message> - <location filename="modlistsortproxy.cpp" line="506"/> + <location filename="modlistsortproxy.cpp" line="508"/> <source>Drag&Drop is only supported when sorting by priority</source> <translation type="unfinished"></translation> </message> @@ -4301,51 +4310,17 @@ p, li { white-space: pre-wrap; } <context> <name>MyFileSystemModel</name> <message> - <location filename="overwriteinfodialog.cpp" line="49"/> + <location filename="overwriteinfodialog.cpp" line="48"/> <source>Overwrites</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="61"/> + <location filename="overwriteinfodialog.cpp" line="60"/> <source>not implemented</source> <translation type="unfinished"></translation> </message> </context> <context> - <name>NXMAccessManager</name> - <message> - <location filename="nxmaccessmanager.cpp" line="128"/> - <location filename="nxmaccessmanager.cpp" line="146"/> - <source>Validating Nexus Connection</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="246"/> - <source>There was a timeout during the request</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="268"/> - <source>Unknown error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="308"/> - <source>Validation failed, please reauthenticate in the Settings -> Nexus tab: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="313"/> - <source>Could not parse response. Invalid JSON.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="319"/> - <source>Unknown error.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> <name>NXMUrl</name> <message> <location filename="../../uibase/src/nxmurl.cpp" line="34"/> @@ -4356,32 +4331,32 @@ p, li { white-space: pre-wrap; } <context> <name>NexusInterface</name> <message> - <location filename="nexusinterface.cpp" line="284"/> + <location filename="nexusinterface.cpp" line="299"/> <source>Failed to guess mod id for "%1", please pick the correct one</source> <translation type="unfinished"></translation> </message> <message> - <location filename="nexusinterface.cpp" line="674"/> + <location filename="nexusinterface.cpp" line="689"/> <source>You must authorize MO2 in Settings -> Nexus to use the Nexus API.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="nexusinterface.cpp" line="683"/> + <location filename="nexusinterface.cpp" line="698"/> <source>You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="nexusinterface.cpp" line="735"/> + <location filename="nexusinterface.cpp" line="754"/> <source>Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="nexusinterface.cpp" line="842"/> + <location filename="nexusinterface.cpp" line="861"/> <source>empty response</source> <translation type="unfinished"></translation> </message> <message> - <location filename="nexusinterface.cpp" line="900"/> + <location filename="nexusinterface.cpp" line="919"/> <source>invalid response</source> <translation type="unfinished"></translation> </message> @@ -4432,27 +4407,27 @@ p, li { white-space: pre-wrap; } <context> <name>NexusTab</name> <message> - <location filename="modinfodialognexus.cpp" line="130"/> + <location filename="modinfodialognexus.cpp" line="131"/> <source>Current Version: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialognexus.cpp" line="134"/> + <location filename="modinfodialognexus.cpp" line="135"/> <source>No update available</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialognexus.cpp" line="166"/> + <location filename="modinfodialognexus.cpp" line="167"/> <source>Tracked</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialognexus.cpp" line="169"/> + <location filename="modinfodialognexus.cpp" line="170"/> <source>Untracked</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialognexus.cpp" line="226"/> + <location filename="modinfodialognexus.cpp" line="283"/> <source> <div style="text-align: center;"> <p>This mod does not have a valid Nexus ID. You can add a custom web @@ -4464,7 +4439,7 @@ p, li { white-space: pre-wrap; } <context> <name>NoConflictListModel</name> <message> - <location filename="modinfodialogconflicts.cpp" line="322"/> + <location filename="modinfodialogconflicts.cpp" line="321"/> <source>File</source> <translation type="unfinished"></translation> </message> @@ -4472,269 +4447,212 @@ p, li { white-space: pre-wrap; } <context> <name>OrganizerCore</name> <message> - <location filename="organizercore.cpp" line="393"/> - <location filename="organizercore.cpp" line="420"/> + <location filename="organizercore.cpp" line="249"/> <source>Failed to write settings</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="394"/> - <source>An error occurred trying to update MO settings to %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="415"/> + <location filename="organizercore.cpp" line="241"/> <source>File is write protected</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="417"/> + <location filename="organizercore.cpp" line="243"/> <source>Invalid file format (probably a bug)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="418"/> + <location filename="organizercore.cpp" line="245"/> <source>Unknown error %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="421"/> + <location filename="organizercore.cpp" line="250"/> <source>An error occurred trying to write back MO settings to %1: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="646"/> - <location filename="organizercore.cpp" line="657"/> + <location filename="organizercore.cpp" line="415"/> + <location filename="organizercore.cpp" line="426"/> <source>Download started</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="660"/> + <location filename="organizercore.cpp" line="429"/> <source>Download failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="958"/> - <location filename="organizercore.cpp" line="995"/> - <location filename="organizercore.cpp" line="1006"/> - <location filename="organizercore.cpp" line="1064"/> + <location filename="organizercore.cpp" line="736"/> + <location filename="organizercore.cpp" line="773"/> + <location filename="organizercore.cpp" line="784"/> + <location filename="organizercore.cpp" line="842"/> <source>Installation cancelled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="959"/> - <location filename="organizercore.cpp" line="1007"/> + <location filename="organizercore.cpp" line="737"/> + <location filename="organizercore.cpp" line="785"/> <source>Another installation is currently in progress.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="971"/> - <location filename="organizercore.cpp" line="1036"/> + <location filename="organizercore.cpp" line="749"/> + <location filename="organizercore.cpp" line="814"/> <source>Installation successful</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="979"/> - <location filename="organizercore.cpp" line="1046"/> + <location filename="organizercore.cpp" line="757"/> + <location filename="organizercore.cpp" line="824"/> <source>Configure Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="980"/> - <location filename="organizercore.cpp" line="1047"/> + <location filename="organizercore.cpp" line="758"/> + <location filename="organizercore.cpp" line="825"/> <source>This mod contains ini tweaks. Do you want to configure them now?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="992"/> - <location filename="organizercore.cpp" line="1057"/> + <location filename="organizercore.cpp" line="770"/> + <location filename="organizercore.cpp" line="835"/> <source>mod not found: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="996"/> - <location filename="organizercore.cpp" line="1065"/> + <location filename="organizercore.cpp" line="774"/> + <location filename="organizercore.cpp" line="843"/> <source>The mod was not installed completely.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1335"/> + <location filename="organizercore.cpp" line="1115"/> <source>file not found: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1348"/> + <location filename="organizercore.cpp" line="1129"/> <source>failed to generate preview for %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1409"/> + <location filename="organizercore.cpp" line="1181"/> <source>Sorry</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1410"/> + <location filename="organizercore.cpp" line="1182"/> <source>Sorry, can't preview anything. This function currently does not support extracting from bsas.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1420"/> + <location filename="organizercore.cpp" line="1192"/> <source>File '%1' not found.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1428"/> + <location filename="organizercore.cpp" line="1200"/> <source>Failed to generate preview for %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1525"/> - <source>Executable not found: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1560"/> - <source>Start Steam?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1561"/> - <source>Steam is required to be running already to correctly start the game. Should MO try to start steam now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1584"/> - <source>Steam: Access Denied</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1585"/> - <source>MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. - -Restart MO as administrator?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1632"/> + <location filename="organizercore.cpp" line="1325"/> <source>Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1640"/> - <source>Windows Event Log Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1641"/> - <source>The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. - -Continue launching %1?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1653"/> - <source>Blacklisted Executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1654"/> - <source>The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. - -Continue launching %1?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="organizercore.cpp" line="1752"/> + <location filename="organizercore.cpp" line="1425"/> <source>No profile set</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2059"/> + <location filename="organizercore.cpp" line="1735"/> <source>Failed to refresh list of esps: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2168"/> + <location filename="organizercore.cpp" line="1844"/> <source>Multiple esps/esls activated, please check that they don't conflict.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2232"/> + <location filename="organizercore.cpp" line="1908"/> <source>You need to be logged in with Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2271"/> + <location filename="organizercore.cpp" line="1947"/> <source>Download?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2272"/> + <location filename="organizercore.cpp" line="1948"/> <source>A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2407"/> - <location filename="organizercore.cpp" line="2456"/> + <location filename="organizercore.cpp" line="2085"/> + <location filename="organizercore.cpp" line="2134"/> <source>failed to update mod list: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2463"/> - <location filename="organizercore.cpp" line="2480"/> + <location filename="organizercore.cpp" line="2141"/> + <location filename="organizercore.cpp" line="2158"/> <source>login successful</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2487"/> + <location filename="organizercore.cpp" line="2168"/> <source>Login failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2488"/> + <location filename="organizercore.cpp" line="2169"/> <source>Login failed, try again?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2497"/> + <location filename="organizercore.cpp" line="2178"/> <source>login failed: %1. Download will not be associated with an account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2505"/> + <location filename="organizercore.cpp" line="2186"/> <source>login failed: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2515"/> + <location filename="organizercore.cpp" line="2196"/> <source>login failed: %1. You need to log-in with Nexus to update MO.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2568"/> + <location filename="organizercore.cpp" line="2249"/> <source>MO1 "Script Extender" load mechanism has left hook.dll in your game folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2571"/> - <location filename="organizercore.cpp" line="2587"/> + <location filename="organizercore.cpp" line="2252"/> + <location filename="organizercore.cpp" line="2268"/> <source>Description missing</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2580"/> + <location filename="organizercore.cpp" line="2261"/> <source><a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2614"/> + <location filename="organizercore.cpp" line="2295"/> <source>failed to save load order: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="2686"/> + <location filename="organizercore.cpp" line="2367"/> <source>The designated write target "%1" is not enabled.</source> <translation type="unfinished"></translation> </message> @@ -4742,12 +4660,12 @@ Continue?</source> <context> <name>OverwriteConflictListModel</name> <message> - <location filename="modinfodialogconflicts.cpp" line="296"/> + <location filename="modinfodialogconflicts.cpp" line="295"/> <source>File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="297"/> + <location filename="modinfodialogconflicts.cpp" line="296"/> <source>Overwritten Mods</source> <translation type="unfinished"></translation> </message> @@ -4770,63 +4688,63 @@ Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="90"/> + <location filename="overwriteinfodialog.cpp" line="89"/> <source>&Delete</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="91"/> + <location filename="overwriteinfodialog.cpp" line="90"/> <source>&Rename</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="92"/> + <location filename="overwriteinfodialog.cpp" line="91"/> <source>&Open</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="93"/> + <location filename="overwriteinfodialog.cpp" line="92"/> <source>&New Folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="114"/> + <location filename="overwriteinfodialog.cpp" line="125"/> <source>mod not found: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="149"/> + <location filename="overwriteinfodialog.cpp" line="160"/> <source>Failed to delete "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="165"/> - <location filename="overwriteinfodialog.cpp" line="171"/> - <location filename="overwriteinfodialog.cpp" line="190"/> - <location filename="overwriteinfodialog.cpp" line="195"/> + <location filename="overwriteinfodialog.cpp" line="176"/> + <location filename="overwriteinfodialog.cpp" line="182"/> + <location filename="overwriteinfodialog.cpp" line="201"/> + <location filename="overwriteinfodialog.cpp" line="206"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="165"/> - <location filename="overwriteinfodialog.cpp" line="190"/> + <location filename="overwriteinfodialog.cpp" line="176"/> + <location filename="overwriteinfodialog.cpp" line="201"/> <source>Are you sure you want to delete "%1"?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="171"/> - <location filename="overwriteinfodialog.cpp" line="195"/> + <location filename="overwriteinfodialog.cpp" line="182"/> + <location filename="overwriteinfodialog.cpp" line="206"/> <source>Are you sure you want to delete the selected files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="240"/> - <location filename="overwriteinfodialog.cpp" line="246"/> + <location filename="overwriteinfodialog.cpp" line="251"/> + <location filename="overwriteinfodialog.cpp" line="257"/> <source>New Folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="overwriteinfodialog.cpp" line="252"/> + <location filename="overwriteinfodialog.cpp" line="263"/> <source>Failed to create "%1"</source> <translation type="unfinished"></translation> </message> @@ -4834,12 +4752,12 @@ Continue?</source> <context> <name>OverwrittenConflictListModel</name> <message> - <location filename="modinfodialogconflicts.cpp" line="309"/> + <location filename="modinfodialogconflicts.cpp" line="308"/> <source>File</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogconflicts.cpp" line="310"/> + <location filename="modinfodialogconflicts.cpp" line="309"/> <source>Providing Mod</source> <translation type="unfinished"></translation> </message> @@ -4847,18 +4765,18 @@ Continue?</source> <context> <name>PluginContainer</name> <message> - <location filename="plugincontainer.cpp" line="330"/> + <location filename="plugincontainer.cpp" line="332"/> <source>Some plugins could not be loaded</source> <translation type="unfinished"></translation> </message> <message> - <location filename="plugincontainer.cpp" line="333"/> - <location filename="plugincontainer.cpp" line="351"/> + <location filename="plugincontainer.cpp" line="335"/> + <location filename="plugincontainer.cpp" line="353"/> <source>Description missing</source> <translation type="unfinished"></translation> </message> <message> - <location filename="plugincontainer.cpp" line="342"/> + <location filename="plugincontainer.cpp" line="344"/> <source>The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:</source> <translation type="unfinished"></translation> </message> @@ -4933,68 +4851,68 @@ Continue?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="466"/> + <location filename="pluginlist.cpp" line="470"/> <source>The file containing locked plugin indices is broken</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="963"/> <location filename="pluginlist.cpp" line="967"/> + <location filename="pluginlist.cpp" line="971"/> <source><b>Origin</b>: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="964"/> + <location filename="pluginlist.cpp" line="968"/> <source><br><b><i>This plugin can't be disabled (enforced by the game).</i></b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="969"/> + <location filename="pluginlist.cpp" line="973"/> <source>Author</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="972"/> + <location filename="pluginlist.cpp" line="976"/> <source>Description</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="975"/> + <location filename="pluginlist.cpp" line="979"/> <source>Missing Masters</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="982"/> + <location filename="pluginlist.cpp" line="986"/> <source>Enabled Masters</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="985"/> + <location filename="pluginlist.cpp" line="989"/> <source>Loads Archives</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="986"/> + <location filename="pluginlist.cpp" line="990"/> <source>There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="991"/> + <location filename="pluginlist.cpp" line="995"/> <source>Loads INI settings</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="992"/> + <location filename="pluginlist.cpp" line="996"/> <source>There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="996"/> + <location filename="pluginlist.cpp" line="1000"/> <source>This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="1171"/> + <location filename="pluginlist.cpp" line="1175"/> <source>failed to restore load order for %1</source> <translation type="unfinished"></translation> </message> @@ -5033,7 +4951,12 @@ Continue?</source> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html></source> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click a notification above to get more details...</p></body></html></source> + <oldsource><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html></oldsource> <translation type="unfinished"></translation> </message> <message> @@ -5042,18 +4965,18 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="problemsdialog.cpp" line="56"/> - <location filename="problemsdialog.cpp" line="57"/> + <location filename="problemsdialog.cpp" line="62"/> + <location filename="problemsdialog.cpp" line="63"/> <source>Fix</source> <translation type="unfinished"></translation> </message> <message> - <location filename="problemsdialog.cpp" line="63"/> + <location filename="problemsdialog.cpp" line="69"/> <source>No guided fix</source> <translation type="unfinished"></translation> </message> <message> - <location filename="problemsdialog.cpp" line="71"/> + <location filename="problemsdialog.cpp" line="77"/> <source>(There are no notifications)</source> <translation type="unfinished"></translation> </message> @@ -5061,17 +4984,17 @@ p, li { white-space: pre-wrap; } <context> <name>Profile</name> <message> - <location filename="profile.cpp" line="81"/> + <location filename="profile.cpp" line="80"/> <source>invalid profile name: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="85"/> + <location filename="profile.cpp" line="84"/> <source>failed to create %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="259"/> + <location filename="profile.cpp" line="257"/> <source>failed to write mod list: %1</source> <translation type="unfinished"></translation> </message> @@ -5081,53 +5004,51 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="290"/> + <location filename="profile.cpp" line="292"/> <source>failed to create tweaked ini: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="315"/> + <location filename="profile.cpp" line="319"/> <source>failed to open %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="374"/> + <location filename="profile.cpp" line="379"/> <source>"%1" is missing or inaccessible</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="419"/> - <location filename="profile.cpp" line="458"/> - <location filename="profile.cpp" line="548"/> - <location filename="profile.cpp" line="569"/> - <location filename="profile.cpp" line="579"/> - <location filename="profile.cpp" line="598"/> - <location filename="profile.cpp" line="608"/> + <location filename="profile.cpp" line="424"/> + <location filename="profile.cpp" line="465"/> + <location filename="profile.cpp" line="557"/> + <location filename="profile.cpp" line="607"/> + <location filename="profile.cpp" line="617"/> <source>invalid mod index: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="540"/> + <location filename="profile.cpp" line="549"/> <source>invalid priority %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="779"/> + <location filename="profile.cpp" line="788"/> <source>Delete profile-specific save games?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="780"/> + <location filename="profile.cpp" line="789"/> <source>Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="810"/> + <location filename="profile.cpp" line="819"/> <source>Missing profile-specific game INI files!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="811"/> + <location filename="profile.cpp" line="820"/> <source>Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want to double-check your settings. Missing files: @@ -5135,12 +5056,12 @@ Missing files: <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="827"/> + <location filename="profile.cpp" line="836"/> <source>Delete profile-specific game INI files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profile.cpp" line="828"/> + <location filename="profile.cpp" line="837"/> <source>Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.)</source> <translation type="unfinished"></translation> </message> @@ -5310,83 +5231,83 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="115"/> - <location filename="profilesdialog.cpp" line="156"/> + <location filename="profilesdialog.cpp" line="121"/> + <location filename="profilesdialog.cpp" line="162"/> <source>failed to create profile: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="165"/> + <location filename="profilesdialog.cpp" line="171"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="165"/> + <location filename="profilesdialog.cpp" line="171"/> <source>Please enter a name for the new profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="176"/> + <location filename="profilesdialog.cpp" line="182"/> <source>failed to copy profile: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="179"/> + <location filename="profilesdialog.cpp" line="185"/> <source>Invalid name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="180"/> + <location filename="profilesdialog.cpp" line="186"/> <source>Invalid profile name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="189"/> + <location filename="profilesdialog.cpp" line="195"/> <source>Deleting active profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="190"/> + <location filename="profilesdialog.cpp" line="196"/> <source>Unable to delete active profile. Please change to a different profile first.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="194"/> + <location filename="profilesdialog.cpp" line="200"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="194"/> + <location filename="profilesdialog.cpp" line="200"/> <source>Are you sure you want to remove this profile (including profile-specific save games, if any)?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="202"/> + <location filename="profilesdialog.cpp" line="208"/> <source>Profile broken</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="203"/> + <location filename="profilesdialog.cpp" line="209"/> <source>This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="235"/> + <location filename="profilesdialog.cpp" line="241"/> <source>Rename Profile</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="235"/> + <location filename="profilesdialog.cpp" line="241"/> <source>New Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="270"/> + <location filename="profilesdialog.cpp" line="276"/> <source>failed to change archive invalidation state: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="304"/> + <location filename="profilesdialog.cpp" line="310"/> <source>failed to determine if invalidation is active: %1</source> <translation type="unfinished"></translation> </message> @@ -5400,12 +5321,12 @@ p, li { white-space: pre-wrap; } </message> <message> <location filename="../../uibase/src/registry.cpp" line="37"/> - <location filename="../../uibase/src/textviewer.cpp" line="133"/> + <location filename="../../uibase/src/textviewer.cpp" line="134"/> <source>Mod Organizer is attempting to write to "%1" which is currently set to read-only. Clear the read-only flag to allow the write?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/textviewer.cpp" line="132"/> + <location filename="../../uibase/src/textviewer.cpp" line="133"/> <source>File is read-only</source> <translation type="unfinished"></translation> </message> @@ -5413,61 +5334,119 @@ p, li { white-space: pre-wrap; } <context> <name>QObject</name> <message> - <location filename="../../uibase/src/report.cpp" line="34"/> - <location filename="../../uibase/src/report.cpp" line="37"/> - <location filename="main.cpp" line="98"/> - <location filename="organizercore.cpp" line="684"/> - <location filename="organizercore.cpp" line="699"/> + <location filename="../../uibase/src/report.cpp" line="38"/> + <location filename="../../uibase/src/report.cpp" line="41"/> + <location filename="main.cpp" line="99"/> + <location filename="organizercore.cpp" line="453"/> + <location filename="organizercore.cpp" line="468"/> + <location filename="settingsdialogdiagnostics.cpp" line="36"/> + <location filename="settingsdialogpaths.cpp" line="71"/> <source>Error</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../../uibase/src/report.cpp" line="289"/> + <source>You can reset these choices by clicking "Reset Dialog Choices" in the General tab of the Settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/report.cpp" line="298"/> + <source>Always ask</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/report.cpp" line="299"/> + <location filename="../../uibase/src/report.cpp" line="306"/> + <source>Remember my choice</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/report.cpp" line="300"/> + <source>Remember my choice for %1</source> + <translation type="unfinished"></translation> + </message> + <message> <location filename="../../uibase/src/safewritefile.cpp" line="33"/> <source>failed to open temporary file</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/utility.cpp" line="88"/> + <location filename="../../uibase/src/utility.cpp" line="67"/> <source>removal of "%1" failed: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/utility.cpp" line="95"/> + <location filename="../../uibase/src/utility.cpp" line="74"/> <source>removal of "%1" failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/utility.cpp" line="99"/> + <location filename="../../uibase/src/utility.cpp" line="78"/> <source>"%1" doesn't exist (remove)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/utility.cpp" line="444"/> - <location filename="../../uibase/src/utility.cpp" line="469"/> + <location filename="../../uibase/src/utility.cpp" line="424"/> + <location filename="../../uibase/src/utility.cpp" line="449"/> <source>failed to create directory "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/utility.cpp" line="453"/> - <location filename="../../uibase/src/utility.cpp" line="476"/> + <location filename="../../uibase/src/utility.cpp" line="433"/> + <location filename="../../uibase/src/utility.cpp" line="456"/> <source>failed to copy "%1" to "%2"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="categories.cpp" line="149"/> + <location filename="../../uibase/src/utility.cpp" line="703"/> + <source>%1 MB</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="703"/> + <source>%1 GB</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="703"/> + <source>%1 TB</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="706"/> + <source>%1 KB</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="724"/> + <source>%1 B/s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="728"/> + <source>%1 KB/s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/utility.cpp" line="732"/> + <source>%1 MB/s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="categories.cpp" line="150"/> <source>Failed to save custom categories</source> <translation type="unfinished"></translation> </message> <message> - <location filename="categories.cpp" line="271"/> - <location filename="categories.cpp" line="306"/> - <location filename="categories.cpp" line="316"/> - <location filename="categories.cpp" line="326"/> + <location filename="categories.cpp" line="272"/> + <location filename="categories.cpp" line="307"/> + <location filename="categories.cpp" line="317"/> + <location filename="categories.cpp" line="327"/> <source>invalid category index: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="categories.cpp" line="337"/> + <location filename="categories.cpp" line="338"/> <source>invalid category id: %1</source> <translation type="unfinished"></translation> </message> @@ -5512,53 +5491,42 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="100"/> + <location filename="filerenamer.cpp" line="104"/> <source>The hidden file "%1" already exists. Replace it?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="103"/> + <location filename="filerenamer.cpp" line="107"/> <source>The visible file "%1" already exists. Replace it?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="113"/> + <location filename="filerenamer.cpp" line="117"/> <source>Replace file?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="152"/> - <location filename="filerenamer.cpp" line="176"/> + <location filename="filerenamer.cpp" line="156"/> + <location filename="filerenamer.cpp" line="180"/> <source>File operation failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="153"/> + <location filename="filerenamer.cpp" line="157"/> <source>Failed to remove "%1". Maybe you lack the required file permissions?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filerenamer.cpp" line="177"/> + <location filename="filerenamer.cpp" line="181"/> <source>failed to rename %1 to %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="filterwidget.cpp" line="48"/> + <location filename="filterwidget.cpp" line="51"/> <source>Filter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="helper.cpp" line="56"/> - <location filename="helper.cpp" line="65"/> - <source>helper failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="helper.cpp" line="81"/> - <source>failed to determine account name</source> - <translation type="unfinished"></translation> - </message> - <message> <location filename="installationmanager.cpp" line="73"/> <location filename="selfupdater.cpp" line="82"/> <source>invalid 7-zip32.dll: %1</source> @@ -5570,460 +5538,782 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="82"/> + <location filename="instancemanager.cpp" line="83"/> <source>Deleting folder</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="83"/> + <location filename="instancemanager.cpp" line="84"/> <source>I'm about to delete the following folder: "%1". Proceed?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="104"/> + <location filename="instancemanager.cpp" line="108"/> <source>Choose Instance to Delete</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="105"/> + <location filename="instancemanager.cpp" line="109"/> <source>Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="118"/> + <location filename="instancemanager.cpp" line="122"/> <source>Are you sure?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="119"/> + <location filename="instancemanager.cpp" line="123"/> <source>Are you really sure you want to delete the Instance "%1" with all its files?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="123"/> + <location filename="instancemanager.cpp" line="127"/> <source>Failed to delete Instance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="124"/> + <location filename="instancemanager.cpp" line="128"/> <source>Could not delete Instance "%1". If the folder was still in use, restart MO and try again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="139"/> + <location filename="instancemanager.cpp" line="143"/> <source>Enter a Name for the new Instance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="140"/> + <location filename="instancemanager.cpp" line="144"/> <source>Enter a new name or select one from the suggested list: (This is just a name for the Instance and can be whatever you wish, the actual game selection will happen on the next screen regardless of chosen name)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="151"/> - <location filename="instancemanager.cpp" line="224"/> + <location filename="instancemanager.cpp" line="155"/> + <location filename="instancemanager.cpp" line="232"/> <source>Canceled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="157"/> + <location filename="instancemanager.cpp" line="161"/> <source>Invalid instance name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="158"/> + <location filename="instancemanager.cpp" line="162"/> <source>The instance name "%1" is invalid. Use the name "%2" instead?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="173"/> + <location filename="instancemanager.cpp" line="177"/> <source>The instance "%1" already exists.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="174"/> + <location filename="instancemanager.cpp" line="178"/> <source>Please choose a different instance name, like: "%1 1" .</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="193"/> + <location filename="instancemanager.cpp" line="201"/> <source>Choose Instance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="194"/> + <location filename="instancemanager.cpp" line="202"/> <source>Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="206"/> + <location filename="instancemanager.cpp" line="214"/> <source>New</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="207"/> + <location filename="instancemanager.cpp" line="215"/> <source>Create a new instance.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="211"/> + <location filename="instancemanager.cpp" line="219"/> <source>Portable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="212"/> + <location filename="instancemanager.cpp" line="220"/> <source>Use MO folder for data.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="216"/> + <location filename="instancemanager.cpp" line="224"/> <source>Manage Instances</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="217"/> + <location filename="instancemanager.cpp" line="225"/> <source>Delete an Instance.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="270"/> - <location filename="profile.cpp" line="69"/> + <location filename="instancemanager.cpp" line="291"/> + <location filename="profile.cpp" line="68"/> <source>failed to create %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="273"/> + <location filename="instancemanager.cpp" line="294"/> <source>Data directory created</source> <translation type="unfinished"></translation> </message> <message> - <location filename="instancemanager.cpp" line="274"/> + <location filename="instancemanager.cpp" line="295"/> <source>New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="52"/> - <source>failed to open %1: %2</source> + <location filename="main.cpp" line="100"/> + <location filename="organizercore.cpp" line="454"/> + <source>Failed to create "%1". Your user account probably lacks permission.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="103"/> - <location filename="loadmechanism.cpp" line="112"/> - <source>file not found: %1</source> + <location filename="main.cpp" line="297"/> + <source>Plugin to handle %1 no longer installed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="149"/> - <source>Failed to delete %1</source> + <location filename="main.cpp" line="310"/> + <location filename="main.cpp" line="352"/> + <location filename="main.cpp" line="368"/> + <source>The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="156"/> - <source>Failed to deactivate script extender loading</source> + <location filename="main.cpp" line="325"/> + <source>Could not use configuration settings for game "%1", path "%2".</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="176"/> - <source>Failed to remove %1: %2</source> + <location filename="main.cpp" line="330"/> + <location filename="main.cpp" line="360"/> + <location filename="main.cpp" line="385"/> + <source>Please select the installation of %1 to manage</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="178"/> - <location filename="loadmechanism.cpp" line="283"/> - <source>Failed to rename %1 to %2</source> + <location filename="main.cpp" line="331"/> + <location filename="main.cpp" line="361"/> + <location filename="main.cpp" line="386"/> + <source>Please select the game to manage</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="185"/> - <source>Failed to deactivate proxy-dll loading</source> + <location filename="main.cpp" line="397"/> + <source>Canceled finding %1 in "%2".</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="233"/> - <location filename="loadmechanism.cpp" line="267"/> - <location filename="loadmechanism.cpp" line="286"/> - <source>Failed to copy %1 to %2</source> + <location filename="main.cpp" line="398"/> + <source>Canceled finding game in "%1".</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="239"/> - <source>Failed to set up script extender loading</source> + <location filename="main.cpp" line="405"/> + <source>%1 not identified in "%2". The directory is required to contain the game binary.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="264"/> - <source>Failed to delete old proxy-dll %1</source> + <location filename="main.cpp" line="414"/> + <source>No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul></source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="279"/> - <source>Failed to overwrite %1</source> + <location filename="main.cpp" line="628"/> + <source>Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="loadmechanism.cpp" line="291"/> - <source>Failed to set up proxy-dll loading</source> + <location filename="main.cpp" line="671"/> + <source>failed to start shortcut: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="99"/> - <location filename="organizercore.cpp" line="685"/> - <source>Failed to create "%1". Your user account probably lacks permission.</source> + <location filename="main.cpp" line="692"/> + <source>failed to start application: %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="298"/> - <source>Plugin to handle %1 no longer installed</source> + <location filename="main.cpp" line="896"/> + <location filename="settingsdialogworkarounds.cpp" line="16"/> + <source>Mod Organizer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="308"/> - <location filename="main.cpp" line="346"/> - <location filename="main.cpp" line="360"/> - <source>The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly.</source> + <location filename="main.cpp" line="897"/> + <source>An instance of Mod Organizer is already running</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="320"/> - <source>Could not use configuration settings for game "%1", path "%2".</source> + <location filename="main.cpp" line="916"/> + <source>Failed to set up instance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="324"/> - <location filename="main.cpp" line="353"/> - <location filename="main.cpp" line="375"/> - <source>Please select the installation of %1 to manage</source> + <location filename="mainwindow.cpp" line="1248"/> + <source>Please use "Help" from the toolbar to get usage instructions to all elements</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="325"/> - <location filename="main.cpp" line="354"/> - <location filename="main.cpp" line="376"/> - <source>Please select the game to manage</source> + <location filename="mainwindow.cpp" line="1756"/> + <location filename="mainwindow.cpp" line="5138"/> + <source><Manage...></source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="384"/> - <source>Canceled finding %1 in "%2".</source> + <location filename="mainwindow.cpp" line="1768"/> + <source>failed to parse profile %1: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="385"/> - <source>Canceled finding game in "%1".</source> + <location filename="modinfodialogesps.cpp" line="315"/> + <source>File Exists</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="391"/> - <source>%1 not identified in "%2". The directory is required to contain the game binary.</source> + <location filename="modinfodialogesps.cpp" line="316"/> + <source>A file with that name exists, please enter a new one</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="399"/> - <source>No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul></source> + <location filename="modinfodialogesps.cpp" line="335"/> + <location filename="modinfodialogesps.cpp" line="383"/> + <source>Failed to move file</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="598"/> - <source>Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)</source> + <location filename="modinfodialogesps.cpp" line="367"/> + <source>Failed to create directory "optional"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="635"/> - <source>failed to start shortcut: %1</source> + <location filename="modinfodialogtextfiles.cpp" line="140"/> + <source>Save changes?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="657"/> - <source>failed to start application: %1</source> + <location filename="modinfodialogtextfiles.cpp" line="141"/> + <source>Save changes to "%1"?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="805"/> - <location filename="settings.cpp" line="1186"/> - <source>Mod Organizer</source> + <location filename="spawn.cpp" line="153"/> + <source>This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="806"/> - <source>An instance of Mod Organizer is already running</source> + <location filename="spawn.cpp" line="160"/> + <source>This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="main.cpp" line="821"/> - <source>Failed to set up instance</source> + <location filename="spawn.cpp" line="165"/> + <source>The file '%1' does not exist.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1247"/> - <source>Please use "Help" from the toolbar to get usage instructions to all elements</source> + <location filename="spawn.cpp" line="181"/> + <location filename="spawn.cpp" line="182"/> + <location filename="spawn.cpp" line="212"/> + <location filename="spawn.cpp" line="213"/> + <source>Cannot start Steam</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1773"/> - <location filename="mainwindow.cpp" line="5247"/> - <source><Manage...></source> + <location filename="spawn.cpp" line="183"/> + <source>The path to the Steam executable cannot be found. You might try reinstalling Steam.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="mainwindow.cpp" line="1785"/> - <source>failed to parse profile %1: %2</source> + <location filename="spawn.cpp" line="189"/> + <location filename="spawn.cpp" line="218"/> + <location filename="spawn.cpp" line="326"/> + <source>Continue without starting Steam</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogesps.cpp" line="314"/> - <source>File Exists</source> + <location filename="spawn.cpp" line="190"/> + <location filename="spawn.cpp" line="219"/> + <source>The program may fail to launch.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogesps.cpp" line="315"/> - <source>A file with that name exists, please enter a new one</source> + <location filename="spawn.cpp" line="232"/> + <source>Cannot launch program</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogesps.cpp" line="334"/> - <location filename="modinfodialogesps.cpp" line="382"/> - <source>Failed to move file</source> + <location filename="spawn.cpp" line="234"/> + <location filename="spawn.cpp" line="259"/> + <location filename="spawn.cpp" line="278"/> + <source>Cannot start %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogesps.cpp" line="366"/> - <source>Failed to create directory "optional"</source> + <location filename="spawn.cpp" line="257"/> + <source>Cannot launch helper</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogtextfiles.cpp" line="140"/> - <source>Save changes?</source> + <location filename="spawn.cpp" line="281"/> + <source>This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files".<byte value="xd"/> +<byte value="xd"/> +You can restart Mod Organizer as administrator and try launching the program again.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="modinfodialogtextfiles.cpp" line="141"/> - <source>Save changes to "%1"?</source> + <location filename="spawn.cpp" line="297"/> + <location filename="spawn.cpp" line="353"/> + <source>Restart Mod Organizer as administrator</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="298"/> + <location filename="spawn.cpp" line="354"/> + <source>You must allow "helper.exe" to make changes to the system.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="311"/> + <source>Launch Steam</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="312"/> + <source>This program requires Steam</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="313"/> + <source>Mod Organizer has detected that this program likely requires Steam to be running to function properly.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="323"/> + <source>Start Steam</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="172"/> - <source>Failed to start "%1"</source> + <location filename="spawn.cpp" line="327"/> + <location filename="spawn.cpp" line="358"/> + <source>The program might fail to run.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="175"/> + <location filename="spawn.cpp" line="340"/> + <source>Steam is running as administrator</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="341"/> + <source>Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator.<byte value="xd"/> +<byte value="xd"/> +You can restart Mod Organizer as administrator and try launching the program again.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="357"/> + <location filename="spawn.cpp" line="384"/> + <location filename="spawn.cpp" line="417"/> + <source>Continue</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="370"/> + <source>Event Log not running</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="371"/> + <source>The Event Log service is not running</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="372"/> + <source>The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="385"/> + <location filename="spawn.cpp" line="418"/> + <source>Your mods might not work.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="398"/> + <source>Blacklisted program</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="399"/> + <source>The program %1 is blacklisted</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="401"/> + <source>The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="421"/> + <source>Change the blacklist</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="spawn.cpp" line="637"/> <source>Waiting</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="176"/> + <location filename="spawn.cpp" line="638"/> <source>Please press OK once you're logged into steam.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="700"/> + <location filename="organizercore.cpp" line="469"/> <source>One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is incompatible with MO2's VFS system.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1249"/> + <location filename="organizercore.cpp" line="1029"/> <source>Select binary</source> <translation type="unfinished"></translation> </message> <message> - <location filename="organizercore.cpp" line="1250"/> + <location filename="organizercore.cpp" line="1030"/> <source>Binary</source> <translation type="unfinished"></translation> </message> <message> - <location filename="plugincontainer.cpp" line="177"/> + <location filename="plugincontainer.cpp" line="178"/> <source>failed to initialize plugin %1: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="plugincontainer.cpp" line="263"/> + <location filename="plugincontainer.cpp" line="264"/> <source>Plugin error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="plugincontainer.cpp" line="264"/> + <location filename="plugincontainer.cpp" line="265"/> <source>It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="542"/> + <location filename="pluginlist.cpp" line="546"/> <source>failed to access %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="pluginlist.cpp" line="556"/> + <location filename="pluginlist.cpp" line="560"/> <source>failed to set file time %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="profilesdialog.cpp" line="96"/> + <location filename="profilesdialog.cpp" line="102"/> <source>Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settings.cpp" line="1193"/> - <source>Script Extender</source> + <location filename="spawn.cpp" line="276"/> + <location filename="spawn.cpp" line="339"/> + <source>Elevation required</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settings.cpp" line="1200"/> - <source>Proxy DLL</source> + <location filename="statusbar.cpp" line="43"/> + <source>This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="spawn.cpp" line="146"/> - <source>failed to spawn "%1"</source> + <location filename="statusbar.cpp" line="62"/> + <source>Loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="spawn.cpp" line="151"/> - <source>Elevation required</source> + <location filename="texteditor.cpp" line="465"/> + <source>&Save</source> <translation type="unfinished"></translation> </message> <message> - <location filename="spawn.cpp" line="152"/> - <source>This process requires elevation to run. -This is a potential security risk so I highly advise you to investigate if -"%1" -can be installed to work without elevation. - -Restart Mod Organizer as an elevated process? -You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually.</source> + <location filename="texteditor.cpp" line="472"/> + <source>&Word wrap</source> <translation type="unfinished"></translation> </message> <message> - <location filename="spawn.cpp" line="163"/> - <location filename="spawn.cpp" line="177"/> - <source>failed to spawn "%1": %2</source> + <location filename="texteditor.cpp" line="477"/> + <source>&Open in Explorer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="statusbar.cpp" line="37"/> - <source>This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens.</source> + <location filename="apiuseraccount.cpp" line="8"/> + <source>Regular</source> <translation type="unfinished"></translation> </message> <message> - <location filename="statusbar.cpp" line="56"/> - <source>Loading...</source> + <location filename="apiuseraccount.cpp" line="11"/> + <source>Premium</source> <translation type="unfinished"></translation> </message> <message> - <location filename="texteditor.cpp" line="462"/> - <source>&Save</source> + <location filename="apiuseraccount.cpp" line="15"/> + <location filename="settingsdialogdiagnostics.cpp" line="54"/> + <source>None</source> <translation type="unfinished"></translation> </message> <message> - <location filename="texteditor.cpp" line="469"/> - <source>&Word wrap</source> + <location filename="nxmaccessmanager.cpp" line="170"/> + <location filename="nxmaccessmanager.cpp" line="360"/> + <source>Connecting to Nexus...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="texteditor.cpp" line="474"/> - <source>&Open in Explorer</source> + <location filename="nxmaccessmanager.cpp" line="173"/> + <source>Waiting for Nexus...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="176"/> + <source>Opened Nexus in browser. +Switch to your browser and accept the request.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="181"/> + <location filename="nxmaccessmanager.cpp" line="363"/> + <source>Finished.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="184"/> + <source>No answer from Nexus. +A firewall might be blocking Mod Organizer.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="189"/> + <source>Nexus closed the connection.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="192"/> + <source>Cancelled.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="366"/> + <source>Invalid JSON</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="369"/> + <source>Bad response</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="372"/> + <source>There was a timeout during the request</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="375"/> + <source>Cancelled</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="412"/> + <source>Failed to request %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settings.cpp" line="1180"/> + <location filename="settings.cpp" line="1223"/> + <source>attempt to store setting for unknown plugin "%1"</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settings.cpp" line="1705"/> + <source>Failed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settings.cpp" line="1706"/> + <source>Failed to start the helper application</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="33"/> + <source>Debug</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="34"/> + <source>Info (recommended)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="35"/> + <source>Warning</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="55"/> + <source>Mini (recommended)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="56"/> + <source>Data</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogdiagnostics.cpp" line="57"/> + <source>Full</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialoggeneral.cpp" line="242"/> + <source>Confirm?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialoggeneral.cpp" line="243"/> + <source>This will reset all the choices you made to dialogs and make them all visible again. Continue?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="223"/> + <source>Disconnected.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="252"/> + <source>Checking API key...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="261"/> + <source>Received API key.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="301"/> + <source>Linked with Nexus successfully.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="341"/> + <location filename="settingsdialognexus.cpp" line="352"/> + <location filename="spawn.cpp" line="193"/> + <location filename="spawn.cpp" line="222"/> + <location filename="spawn.cpp" line="301"/> + <location filename="spawn.cpp" line="330"/> + <location filename="spawn.cpp" line="361"/> + <location filename="spawn.cpp" line="388"/> + <location filename="spawn.cpp" line="424"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="344"/> + <location filename="settingsdialognexus.cpp" line="360"/> + <location filename="settingsdialognexus.cpp" line="367"/> + <source>Enter API Key Manually</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="349"/> + <location filename="settingsdialognexus.cpp" line="357"/> + <location filename="settingsdialognexus.cpp" line="364"/> + <source>Connect to Nexus</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialognexus.cpp" line="390"/> + <location filename="settingsdialognexus.cpp" line="391"/> + <location filename="settingsdialognexus.cpp" line="392"/> + <location filename="settingsdialognexus.cpp" line="393"/> + <location filename="settingsdialognexus.cpp" line="394"/> + <source>N/A</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="72"/> + <source>Failed to create "%1", you may not have the necessary permissions. Path remains unchanged.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="107"/> + <source>Select base directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="118"/> + <source>Select download directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="129"/> + <source>Select mod directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="140"/> + <source>Select cache directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="151"/> + <source>Select profiles directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="162"/> + <source>Select overwrite directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogpaths.cpp" line="172"/> + <source>Select game executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogworkarounds.cpp" line="76"/> + <source>Executables Blacklist</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogworkarounds.cpp" line="77"/> + <source>Enter one executable per line to be blacklisted from the virtual file system. +Mods and other virtualized files will not be visible to these executables and +any executables launched by them. + +Example: + Chrome.exe + Firefox.exe</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogworkarounds.cpp" line="121"/> + <source>Restart Mod Organizer?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialogworkarounds.cpp" line="122"/> + <source>In order to reset the geometry, Mod Organizer must be restarted. +Restart now?</source> <translation type="unfinished"></translation> </message> </context> @@ -6068,13 +6358,13 @@ You will be asked if you want to allow helper.exe to make changes to the system. <context> <name>QuestionBoxMemory</name> <message> - <location filename="../../uibase/src/questionboxmemory.ui" line="88"/> + <location filename="../../uibase/src/questionboxmemory.ui" line="106"/> <source>Remember selection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../uibase/src/questionboxmemory.ui" line="95"/> - <source>Remember selection only for</source> + <location filename="../../uibase/src/questionboxmemory.ui" line="113"/> + <source>Remember selection only for %1</source> <translation type="unfinished"></translation> </message> </context> @@ -6199,46 +6489,6 @@ Select Show Details option to see the full change-log.</source> </message> </context> <context> - <name>Settings</name> - <message> - <location filename="settings.cpp" line="146"/> - <source>Failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="146"/> - <source>Failed to start the helper application</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="566"/> - <location filename="settings.cpp" line="585"/> - <source>attempt to store setting for unknown plugin "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="783"/> - <source>Restart Mod Organizer?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="784"/> - <source>In order to finish configuration changes, MO must be restarted. -Restart it now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="967"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="968"/> - <source>Failed to create "%1", you may not have the necessary permission. path remains unchanged.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> <name>SettingsDialog</name> <message> <location filename="settingsdialog.ui" line="14"/> @@ -6291,382 +6541,433 @@ p, li { white-space: pre-wrap; } </message> <message> <location filename="settingsdialog.ui" line="79"/> - <source>If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). + <source>If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports.</source> + <oldsource>If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. -If you use pre-releases, never contact me directly by e-mail or via private messages!</source> +If you use pre-releases, never contact me directly by e-mail or via private messages!</oldsource> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="86"/> + <location filename="settingsdialog.ui" line="82"/> <source>Install Pre-releases (Betas)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="93"/> + <location filename="settingsdialog.ui" line="89"/> <source>User interface</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="99"/> + <location filename="settingsdialog.ui" line="95"/> <source>Colors</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="105"/> - <location filename="settingsdialog.ui" line="108"/> + <location filename="settingsdialog.ui" line="101"/> + <location filename="settingsdialog.ui" line="104"/> <source>When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="111"/> + <location filename="settingsdialog.ui" line="107"/> <source>Show mod list separator colors on the scrollbar</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="121"/> + <location filename="settingsdialog.ui" line="117"/> <source>Plugin is Contained in selected Mod</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="128"/> + <location filename="settingsdialog.ui" line="124"/> <source>Is overwritten (loose files)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="135"/> + <location filename="settingsdialog.ui" line="131"/> <source>Is overwriting (loose files)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="142"/> + <location filename="settingsdialog.ui" line="138"/> <source>Reset Colors</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="149"/> + <location filename="settingsdialog.ui" line="145"/> <source>Mod Contains selected Plugin</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="156"/> + <location filename="settingsdialog.ui" line="152"/> <source>Is overwritten (archive files)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="163"/> + <location filename="settingsdialog.ui" line="159"/> <source>Is overwriting (archive files)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="173"/> - <location filename="settingsdialog.ui" line="176"/> + <location filename="settingsdialog.ui" line="169"/> + <location filename="settingsdialog.ui" line="172"/> <source>Modify the categories available to arrange your mods.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="179"/> + <location filename="settingsdialog.ui" line="175"/> <source>Configure Mod Categories</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="192"/> + <location filename="settingsdialog.ui" line="188"/> <source>Reset stored information from dialogs.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="195"/> + <location filename="settingsdialog.ui" line="191"/> <source>This will make all dialogs show up again where you checked the "Remember selection"-box.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="198"/> - <source>Reset Dialogs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="205"/> + <location filename="settingsdialog.ui" line="201"/> <source>If checked, the download interface will be more compact.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="208"/> + <location filename="settingsdialog.ui" line="204"/> <source>Compact Download Interface</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="228"/> + <location filename="settingsdialog.ui" line="224"/> <source>If checked, the download list will display meta information instead of file names.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="231"/> + <location filename="settingsdialog.ui" line="227"/> <source>Download Meta Information</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="242"/> + <location filename="settingsdialog.ui" line="238"/> <source>Paths</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="250"/> - <location filename="settingsdialog.ui" line="267"/> - <location filename="settingsdialog.ui" line="364"/> - <location filename="settingsdialog.ui" line="422"/> + <location filename="settingsdialog.ui" line="246"/> + <location filename="settingsdialog.ui" line="263"/> + <location filename="settingsdialog.ui" line="360"/> + <location filename="settingsdialog.ui" line="418"/> <source>...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="277"/> + <location filename="settingsdialog.ui" line="273"/> <source>Caches</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="284"/> + <location filename="settingsdialog.ui" line="280"/> <source>Overwrite</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="291"/> - <location filename="settingsdialog.ui" line="294"/> + <location filename="settingsdialog.ui" line="287"/> + <location filename="settingsdialog.ui" line="290"/> <source>Directory where downloads are stored.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="311"/> + <location filename="settingsdialog.ui" line="307"/> <source>Downloads</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="331"/> + <location filename="settingsdialog.ui" line="327"/> <source>Profiles</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="354"/> + <location filename="settingsdialog.ui" line="350"/> <source>Directory where mods are stored.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="357"/> + <location filename="settingsdialog.ui" line="353"/> <source>Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name).</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="381"/> + <location filename="settingsdialog.ui" line="377"/> <source>Mods</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="401"/> + <location filename="settingsdialog.ui" line="397"/> <source>Managed Game</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="408"/> + <location filename="settingsdialog.ui" line="404"/> <source>Base Directory</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="415"/> + <location filename="settingsdialog.ui" line="411"/> <source>Use %BASE_DIR% to refer to the Base Directory.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="444"/> + <location filename="settingsdialog.ui" line="440"/> <source>Important: All directories have to be writable!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="452"/> - <location filename="settingsdialog.ui" line="468"/> + <location filename="settingsdialog.ui" line="448"/> <source>Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="458"/> - <source>Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="461"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. Clicking &quot;Connect to Nexus&quot; will open a Nexus webpage to authorise Mod Organizer. You will need to be logged into your Nexus account. The authorisation is stored in the Windows Credential Manager. Your Nexus username and password are not required or stored by Mod Organizer.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="476"/> + <location filename="settingsdialog.ui" line="590"/> <source>Connect to Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="506"/> + <location filename="settingsdialog.ui" line="597"/> <source>Manually enter the API key and try to login</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="509"/> + <location filename="settingsdialog.ui" line="600"/> <source>Enter API Key Manually</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="516"/> + <location filename="settingsdialog.ui" line="607"/> <source>Clear the stored Nexus API key and force reauthorization.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="519"/> + <location filename="settingsdialog.ui" line="610"/> <source>Disconnect from Nexus</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="530"/> + <location filename="settingsdialog.ui" line="721"/> + <location filename="settingsdialog.ui" line="724"/> + <source><html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="745"/> <source>Remove cache and cookies.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="533"/> + <location filename="settingsdialog.ui" line="748"/> <source>Clear Cache</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="564"/> + <location filename="settingsdialog.ui" line="685"/> <source>Disable automatic internet features</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="567"/> + <location filename="settingsdialog.ui" line="194"/> + <source>Reset Dialog Choices</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="469"/> + <source>Nexus Account</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="478"/> + <source>User ID:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="488"/> + <source>id</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="495"/> + <source>Name:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="502"/> + <source>name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="509"/> + <source>Account:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="516"/> + <source>account</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="526"/> + <source>Statistics</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="535"/> + <source>Daily requests:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="542"/> + <source>daily requests</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="549"/> + <source>Hourly requests:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="556"/> + <source>hourly requests</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="569"/> + <source>Nexus Connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="settingsdialog.ui" line="688"/> <source>Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="570"/> + <location filename="settingsdialog.ui" line="691"/> <source>Offline Mode</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="577"/> + <location filename="settingsdialog.ui" line="698"/> <source>Use a proxy for network connections.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="580"/> + <location filename="settingsdialog.ui" line="701"/> <source>Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="583"/> + <location filename="settingsdialog.ui" line="704"/> <source>Use HTTP Proxy (Uses System Settings)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="590"/> + <location filename="settingsdialog.ui" line="711"/> <source>Endorsement Integration</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="600"/> - <location filename="settingsdialog.ui" line="603"/> - <source><html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="606"/> + <location filename="settingsdialog.ui" line="727"/> <source>Hide API Request Counter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="617"/> + <location filename="settingsdialog.ui" line="738"/> <source>Associate with "Download with manager" links</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="646"/> + <location filename="settingsdialog.ui" line="781"/> <source>Known Servers (updated on download)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="667"/> + <location filename="settingsdialog.ui" line="802"/> <source>Preferred Servers (Drag & Drop)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="702"/> + <location filename="settingsdialog.ui" line="824"/> <source>Steam</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="708"/> + <location filename="settingsdialog.ui" line="830"/> <source>Username</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="718"/> - <source>Password</source> + <location filename="settingsdialog.ui" line="856"/> + <source><html><head/><body><p>If you save your steam user ID and password here, they will be used when logging into steam.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="748"/> - <source>If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure.</source> + <location filename="settingsdialog.ui" line="866"/> + <source>Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="775"/> + <location filename="settingsdialog.ui" line="881"/> <source>Plugins</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="800"/> + <location filename="settingsdialog.ui" line="906"/> <source>Author:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="814"/> + <location filename="settingsdialog.ui" line="920"/> <source>Version:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="828"/> + <location filename="settingsdialog.ui" line="934"/> <source>Description:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="866"/> + <location filename="settingsdialog.ui" line="972"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="871"/> + <location filename="settingsdialog.ui" line="977"/> <source>Value</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="883"/> + <location filename="settingsdialog.ui" line="989"/> <source>Blacklisted Plugins (use <del> to remove):</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="894"/> + <location filename="settingsdialog.ui" line="1000"/> <source>Workarounds</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="902"/> + <location filename="settingsdialog.ui" line="1008"/> <source>Steam App ID</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="922"/> + <location filename="settingsdialog.ui" line="1028"/> <source>The Steam AppID for your game</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="925"/> + <location filename="settingsdialog.ui" line="1031"/> <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6682,17 +6983,17 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="956"/> + <location filename="settingsdialog.ui" line="1062"/> <source>Load Mechanism</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="976"/> + <location filename="settingsdialog.ui" line="1082"/> <source>Select loading mechanism. See help for details.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="979"/> + <location filename="settingsdialog.ui" line="1085"/> <source>Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -6703,28 +7004,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="999"/> + <location filename="settingsdialog.ui" line="1105"/> <source>Enforces that inactive ESPs and ESMs are never loaded.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1002"/> + <location filename="settingsdialog.ui" line="1108"/> <source>It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1006"/> + <location filename="settingsdialog.ui" line="1112"/> <source>Hide inactive ESPs/ESMs</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1013"/> + <location filename="settingsdialog.ui" line="1119"/> <source>Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1016"/> + <location filename="settingsdialog.ui" line="1122"/> <source>By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -6732,66 +7033,66 @@ If you disable this feature, MO will only display official DLCs this way. Please <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1022"/> + <location filename="settingsdialog.ui" line="1128"/> <source>Display mods installed outside MO</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1032"/> + <location filename="settingsdialog.ui" line="1138"/> <source>If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1035"/> + <location filename="settingsdialog.ui" line="1141"/> <source>If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1039"/> + <location filename="settingsdialog.ui" line="1145"/> <source>Force-enable game files</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1049"/> - <location filename="settingsdialog.ui" line="1052"/> + <location filename="settingsdialog.ui" line="1155"/> + <location filename="settingsdialog.ui" line="1158"/> <source>Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1055"/> + <location filename="settingsdialog.ui" line="1161"/> <source>Lock GUI when running executable</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1065"/> + <location filename="settingsdialog.ui" line="1171"/> <source>Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1068"/> + <location filename="settingsdialog.ui" line="1174"/> <source><html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html></source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1071"/> + <location filename="settingsdialog.ui" line="1177"/> <source>Enable parsing of Archives (Experimental Feature)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1083"/> - <location filename="settingsdialog.ui" line="1087"/> + <location filename="settingsdialog.ui" line="1189"/> + <location filename="settingsdialog.ui" line="1193"/> <source>For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1091"/> + <location filename="settingsdialog.ui" line="1197"/> <source>Back-date BSAs</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1102"/> + <location filename="settingsdialog.ui" line="1208"/> <source>Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -6800,48 +7101,48 @@ programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1109"/> + <location filename="settingsdialog.ui" line="1215"/> <source>Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1112"/> + <location filename="settingsdialog.ui" line="1218"/> <source>Configure Executables Blacklist</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1122"/> - <location filename="settingsdialog.ui" line="1125"/> + <location filename="settingsdialog.ui" line="1228"/> + <location filename="settingsdialog.ui" line="1231"/> <source>Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1128"/> + <location filename="settingsdialog.ui" line="1234"/> <source>Reset Window Geometries</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1151"/> + <location filename="settingsdialog.ui" line="1254"/> <source>These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1162"/> + <location filename="settingsdialog.ui" line="1265"/> <source>Diagnostics</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1170"/> + <location filename="settingsdialog.ui" line="1273"/> <source>Max Dumps To Keep</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1190"/> + <location filename="settingsdialog.ui" line="1293"/> <source>Maximum number of crash dumps to keep on disk. Use 0 for unlimited.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1193"/> + <location filename="settingsdialog.ui" line="1296"/> <source> Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -6849,12 +7150,12 @@ programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1205"/> + <location filename="settingsdialog.ui" line="1308"/> <source>Hint: right click link and copy link location</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1208"/> + <location filename="settingsdialog.ui" line="1311"/> <source> Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -6864,17 +7165,17 @@ programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1225"/> + <location filename="settingsdialog.ui" line="1331"/> <source>Crash Dumps</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1232"/> + <location filename="settingsdialog.ui" line="1338"/> <source>Decides which type of crash dumps are collected when injected processes crash.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1235"/> + <location filename="settingsdialog.ui" line="1341"/> <source> Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6885,37 +7186,17 @@ programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1245"/> - <source>None</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1250"/> - <source>Mini (recommended)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1255"/> - <source>Data</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1260"/> - <source>Full</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1288"/> + <location filename="settingsdialog.ui" line="1374"/> <source>Log Level</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1295"/> + <location filename="settingsdialog.ui" line="1381"/> <source>Decides the amount of data printed to "ModOrganizer.log"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1298"/> + <location filename="settingsdialog.ui" line="1384"/> <source> Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -6923,102 +7204,26 @@ programs you are intentionally running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1305"/> - <source>Debug</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1310"/> - <source>Info (recommended)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="1315"/> - <source>Warning</source> + <location filename="settingsdialog.cpp" line="92"/> + <source>Restart Mod Organizer?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.ui" line="1320"/> - <location filename="settingsdialog.cpp" line="492"/> - <source>Error</source> + <location filename="settingsdialog.cpp" line="93"/> + <source>In order to finish configuration changes, MO must be restarted. +Restart it now?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.cpp" line="170"/> + <location filename="settingsdialog.cpp" line="128"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> <message> - <location filename="settingsdialog.cpp" line="171"/> + <location filename="settingsdialog.cpp" line="129"/> <source>Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed?</source> <translation type="unfinished"></translation> </message> - <message> - <location filename="settingsdialog.cpp" line="207"/> - <source>Executables Blacklist</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="208"/> - <source>Enter one executable per line to be blacklisted from the virtual file system. -Mods and other virtualized files will not be visible to these executables and -any executables launched by them. - -Example: - Chrome.exe - Firefox.exe</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="241"/> - <source>Select base directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="252"/> - <source>Select download directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="263"/> - <source>Select mod directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="274"/> - <source>Select cache directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="285"/> - <source>Select profiles directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="296"/> - <source>Select overwrite directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="306"/> - <source>Select game executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="385"/> - <source>Confirm?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="386"/> - <source>This will make all dialogs show up again where you checked the "Remember selection"-box. Continue?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="493"/> - <source>Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize.</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>SingleInstance</name> @@ -7061,22 +7266,60 @@ Example: <translation type="unfinished"></translation> </message> <message> - <location filename="syncoverwritedialog.cpp" line="96"/> + <location filename="syncoverwritedialog.cpp" line="97"/> <source><don't sync></source> <translation type="unfinished"></translation> </message> <message> - <location filename="syncoverwritedialog.cpp" line="148"/> + <location filename="syncoverwritedialog.cpp" line="149"/> <source>failed to remove %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="syncoverwritedialog.cpp" line="150"/> + <location filename="syncoverwritedialog.cpp" line="151"/> <source>failed to move %1 to %2</source> <translation type="unfinished"></translation> </message> </context> <context> + <name>TaskDialog</name> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="20"/> + <source>Dialog</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="56"/> + <source>icon</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="102"/> + <source>dummy main text</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="146"/> + <source>dummy content text</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="206"/> + <source>dummy button</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="234"/> + <source>dummy checkbox</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../uibase/src/taskdialog.ui" line="320"/> + <source>Details</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>TextViewer</name> <message> <location filename="../../uibase/src/textviewer.ui" line="14"/> @@ -7162,22 +7405,22 @@ On Windows XP: <translation type="unfinished"></translation> </message> <message> - <location filename="transfersavesdialog.cpp" line="119"/> + <location filename="transfersavesdialog.cpp" line="120"/> <source>Characters for profile %1</source> <translation type="unfinished"></translation> </message> <message> - <location filename="transfersavesdialog.cpp" line="159"/> + <location filename="transfersavesdialog.cpp" line="160"/> <source>Overwrite</source> <translation type="unfinished"></translation> </message> <message> - <location filename="transfersavesdialog.cpp" line="160"/> + <location filename="transfersavesdialog.cpp" line="161"/> <source>Overwrite the file "%1"</source> <translation type="unfinished"></translation> </message> <message> - <location filename="transfersavesdialog.cpp" line="319"/> + <location filename="transfersavesdialog.cpp" line="320"/> <source>Confirm</source> <translation type="unfinished"></translation> </message> @@ -7185,12 +7428,25 @@ On Windows XP: <context> <name>UsvfsConnector</name> <message> - <location filename="usvfsconnector.cpp" line="163"/> + <location filename="usvfsconnector.cpp" line="213"/> <source>Preparing vfs</source> <translation type="unfinished"></translation> </message> </context> <context> + <name>ValidationProgressDialog</name> + <message> + <location filename="nxmaccessmanager.cpp" line="58"/> + <source>Validating Nexus Connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="nxmaccessmanager.cpp" line="66"/> + <source>Hide</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>WaitingOnCloseDialog</name> <message> <location filename="waitingonclosedialog.ui" line="14"/> diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 91e16716..0da5b604 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -33,7 +33,6 @@ #include "lockeddialog.h" #include "instancemanager.h" #include <scriptextender.h> -#include "helper.h" #include "previewdialog.h" #include <QApplication> @@ -135,34 +134,6 @@ static DWORD getProcessParentID(DWORD pid) return res; } -static void startSteam(QWidget *widget) -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - exe = QString("\"%1\"").arg(exe); - // See if username and password supplied. If so, pass them into steam. - QStringList args; - QString username; - QString password; - if (Settings::instance().steam().login(username, password)) { - args << "-login"; - args << username; - if (password != "") { - args << password; - } - } - if (!QProcess::startDetached(exe, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); - } else { - QMessageBox::information( - widget, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); - } - } -} - template <typename InputIterator> QStringList toStringList(InputIterator current, InputIterator end) { @@ -173,86 +144,6 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -bool checkService() -{ - SC_HANDLE serviceManagerHandle = NULL; - SC_HANDLE serviceHandle = NULL; - LPSERVICE_STATUS_PROCESS serviceStatus = NULL; - LPQUERY_SERVICE_CONFIG serviceConfig = NULL; - bool serviceRunning = true; - - DWORD bytesNeeded; - - try { - serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceManagerHandle) { - log::warn("failed to open service manager (query status) (error {})", GetLastError()); - throw 1; - } - - serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceHandle) { - log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); - throw 2; - } - - if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service config (error {})", GetLastError()); - throw 3; - } - - DWORD serviceConfigSize = bytesNeeded; - serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); - if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - log::warn("failed to query service config (error {})", GetLastError()); - throw 4; - } - - if (serviceConfig->dwStartType == SERVICE_DISABLED) { - log::error("Windows Event Log service is disabled!"); - serviceRunning = false; - } - - if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service status (error {})", GetLastError()); - throw 5; - } - - DWORD serviceStatusSize = bytesNeeded; - serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); - if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - log::warn("failed to query service status (error {})", GetLastError()); - throw 6; - } - - if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - log::error("Windows Event Log service is not running"); - serviceRunning = false; - } - } - catch (int e) { - UNUSED_VAR(e); - serviceRunning = false; - } - - if (serviceStatus) { - LocalFree(serviceStatus); - } - if (serviceConfig) { - LocalFree(serviceConfig); - } - if (serviceHandle) { - CloseServiceHandle(serviceHandle); - } - if (serviceManagerHandle) { - CloseServiceHandle(serviceManagerHandle); - } - - return serviceRunning; -} - OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) @@ -361,67 +252,6 @@ void OrganizerCore::storeSettings() } } -bool OrganizerCore::testForSteam(bool *found, bool *access) -{ - HANDLE hProcessSnap; - HANDLE hProcess; - PROCESSENTRY32 pe32; - DWORD lastError; - - if (found == nullptr || access == nullptr) { - return false; - } - - // Take a snapshot of all processes in the system. - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - lastError = GetLastError(); - log::error("unable to get snapshot of processes (error {})", lastError); - return false; - } - - // Retrieve information about the first process, - // and exit if unsuccessful - pe32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(hProcessSnap, &pe32)) { - lastError = GetLastError(); - log::error("unable to get first process (error {})", lastError); - CloseHandle(hProcessSnap); - return false; - } - - *found = false; - *access = true; - - // Now walk the snapshot of processes, and - // display information about each process in turn - do { - if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || - (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { - - *found = true; - - // Try to open the process to determine if MO has the proper access - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pe32.th32ProcessID); - if (hProcess == NULL) { - lastError = GetLastError(); - if (lastError == ERROR_ACCESS_DENIED) { - *access = false; - } - } else { - CloseHandle(hProcess); - } - break; - } - -} while(Process32Next(hProcessSnap, &pe32)); - -CloseHandle(hProcessSnap); -return true; - -} - void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { @@ -1451,95 +1281,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, const QString &customOverwrite, const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries) { - prepareStart(); + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.hooked = true; - if (!binary.exists()) { - reportError( - tr("Executable not found: %1").arg(qUtf8Printable(binary.absoluteFilePath()))); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.steam().appID()).c_str()); - } + prepareStart(); QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { window = nullptr; } - // This could possibly be extracted somewhere else but it's probably for when - // we have more than one provider of game registration. - if ((QFileInfo( - managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")) - .exists() - || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( - "steam_api64.dll")) - .exists()) - && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { - - bool steamFound = true; - bool steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } - - if (!steamFound) { - QDialogButtonBox::StandardButton result; - result = 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::Cancel); - if (result == QDialogButtonBox::Yes) { - startSteam(window); - - // double-check that Steam is started and MO has access - steamFound = true; - steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } else if (!steamFound) { - log::error("could not find Steam"); - } - - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBinary(window, sp)) { + return INVALID_HANDLE_VALUE; + } - if (!steamAccess) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(window, "steamAdminQuery", binary.fileName(), - tr("Steam: Access Denied"), - tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - log::error("unable to get current directory (error {})", GetLastError()); - cwd[0] = L'\0'; - } - if (!Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { - log::error("unable to relaunch MO as admin"); - return INVALID_HANDLE_VALUE; - } - qApp->exit(0); - return INVALID_HANDLE_VALUE; - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { + return INVALID_HANDLE_VALUE; } while (m_DirectoryUpdate) { @@ -1566,32 +1326,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, return INVALID_HANDLE_VALUE; } - // Check if the Windows Event Logging service is running. For some reason, this seems to be - // critical to the successful running of usvfs. - if (!checkService()) { - if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(), - tr("Windows Event Log Error"), - tr("The Windows Event Log service is disabled and/or not running. This prevents" - " USVFS from running properly. Your mods may not be working in the executable" - " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } + if (!spawn::checkEnvironment(window, sp)) { + return INVALID_HANDLE_VALUE; } - for (auto exec : settings().executablesBlacklist().split(";")) { - if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(window, QString("blacklistedExecutable"), binary.fileName(), - tr("Blacklisted Executable"), - tr("The executable you are attempted to launch is blacklisted in the virtual file" - " system. This will likely prevent the executable, and any executables that are" - " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBlacklist(window, sp, m_Settings)) { + return INVALID_HANDLE_VALUE; } QString modsPath = settings().paths().mods(); @@ -1626,13 +1366,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - log::debug("Spawning proxyed process <{}>", cmdline); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); - return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), - cmdline, QCoreApplication::applicationDirPath(), true); + return spawn::startBinary(window, sp); } else { log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return startBinary(binary, arguments, currentDirectory, true); + return spawn::startBinary(window, sp); } } else { log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); @@ -255,3 +255,4 @@ #include <QWhatsThisClickedEvent> #include <QWidget> #include <QWidgetAction> +#include <QStorageInfo> diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 33423225..c6c61da3 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -437,7 +437,10 @@ void PluginList::readLockedOrderFrom(const QString &fileName) }
file.open(QIODevice::ReadOnly);
+
+ int lineNumber = 0;
while (!file.atEnd()) {
+ ++lineNumber;
QByteArray line = file.readLine();
if ((line.size() > 0) && (line.at(0) != '#')) {
QList<QByteArray> fields = line.split('|');
@@ -463,6 +466,7 @@ void PluginList::readLockedOrderFrom(const QString &fileName) }
}
} else {
+ log::error("locked order file: invalid line #{} '{}'", lineNumber, QString::fromUtf8(line));
reportError(tr("The file containing locked plugin indices is broken"));
break;
}
diff --git a/src/settings.cpp b/src/settings.cpp index 1f066100..7cea52fb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -222,6 +222,17 @@ QString Settings::executablesBlacklist() const return get<QString>(m_Settings, "Settings", "executable_blacklist", def); } +bool Settings::isExecutableBlacklisted(const QString& s) const +{ + for (auto exec : executablesBlacklist().split(";")) { + if (exec.compare(s, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + void Settings::setExecutablesBlacklist(const QString& s) { set(m_Settings, "Settings", "executable_blacklist", s); @@ -935,7 +946,7 @@ QuestionBoxMemory::Button WidgetSettings::questionButton( if (!filename.isEmpty()) { const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional<int>(m_Settings, sectionName, filename)) { + if (auto v=getOptional<int>(m_Settings, sectionName, fileSetting)) { return static_cast<QuestionBoxMemory::Button>(*v); } } @@ -1219,6 +1230,7 @@ void PluginSettings::setPersistent( m_Settings.sync(); } } + void PluginSettings::addBlacklist(const QString &fileName) { m_PluginBlacklist.insert(fileName); diff --git a/src/settings.h b/src/settings.h index 91b87e29..cd478a5b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -32,13 +32,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. namespace MOBase { class IPlugin; class IPluginGame; + class ExpanderWidget; } class QSplitter; class ServerList; class Settings; -class ExpanderWidget; // helper class that calls restoreGeometry() in the constructor and @@ -153,8 +153,8 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - void saveState(const ExpanderWidget* expander); - bool restoreState(ExpanderWidget* expander) const; + void saveState(const MOBase::ExpanderWidget* expander); + bool restoreState(MOBase::ExpanderWidget* expander) const; void saveVisibility(const QWidget* w); bool restoreVisibility(QWidget* w, std::optional<bool> def={}) const; @@ -678,6 +678,7 @@ public: // by MO but given to usvfs when starting an executable // QString executablesBlacklist() const; + bool isExecutableBlacklisted(const QString& s) const; void setExecutablesBlacklist(const QString& s); // ? looks obsolete, only used by dead code diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 1d3d4a39..8fb25b1c 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) - , m_PluginContainer(pluginContainer) - , m_keyChanged(false) + , m_pluginContainer(pluginContainer) + , m_restartNeeded(false) { ui->setupUi(this); @@ -47,6 +47,25 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti m_tabs.push_back(std::unique_ptr<SettingsTab>(new WorkaroundsSettingsTab(settings, *this))); } +PluginContainer* SettingsDialog::pluginContainer() +{ + return m_pluginContainer; +} + +QWidget* SettingsDialog::parentWidgetForDialogs() +{ + if (isVisible()) { + return this; + } else { + return parentWidget(); + } +} + +void SettingsDialog::setRestartNeeded() +{ + m_restartNeeded = true; +} + int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); @@ -68,13 +87,8 @@ int SettingsDialog::exec() } } - bool restartNeeded = false; - if (getApiKeyChanged()) { - restartNeeded = true; - } - - if (restartNeeded) { - if (QMessageBox::question(nullptr, + if (m_restartNeeded) { + if (QMessageBox::question(parentWidgetForDialogs(), tr("Restart Mod Organizer?"), tr("In order to finish configuration changes, MO must be restarted.\n" "Restart it now?"), @@ -111,7 +125,7 @@ void SettingsDialog::accept() QDir::fromNativeSeparators( Settings::instance().paths().mods(true))) && (QMessageBox::question( - nullptr, tr("Confirm"), + parentWidgetForDialogs(), tr("Confirm"), tr("Changing the mod directory affects all your profiles! " "Mods not present (or named differently) in the new location " "will be disabled in all profiles. " @@ -124,11 +138,6 @@ void SettingsDialog::accept() TutorableDialog::accept(); } -bool SettingsDialog::getApiKeyChanged() -{ - return m_keyChanged; -} - SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) : ui(d.ui), m_settings(s), m_dialog(d) @@ -146,3 +155,8 @@ SettingsDialog& SettingsTab::dialog() { return m_dialog; } + +QWidget* SettingsTab::parentWidget() +{ + return m_dialog.parentWidgetForDialogs(); +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 6a99cb8d..e89da665 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -42,6 +42,7 @@ protected: Settings& settings(); SettingsDialog& dialog(); + QWidget* parentWidget(); private: Settings& m_settings; @@ -56,11 +57,12 @@ private: **/ class SettingsDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + friend class SettingsTab; public: explicit SettingsDialog( - PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0); + PluginContainer* pluginContainer, Settings& settings, QWidget* parent = 0); ~SettingsDialog(); @@ -70,23 +72,21 @@ public: */ QString getColoredButtonStyleSheet() const; - // temp - Ui::SettingsDialog *ui; - bool m_keyChanged; - PluginContainer *m_PluginContainer; + PluginContainer* pluginContainer(); + QWidget* parentWidgetForDialogs(); + void setRestartNeeded(); int exec() override; public slots: virtual void accept(); -public: - bool getApiKeyChanged(); - private: Settings& m_settings; std::vector<std::unique_ptr<SettingsTab>> m_tabs; - + Ui::SettingsDialog* ui; + bool m_restartNeeded; + PluginContainer* m_pluginContainer; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 1a3726fb..40079441 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -191,7 +191,7 @@ p, li { white-space: pre-wrap; } <string>This will make all dialogs show up again where you checked the "Remember selection"-box.</string> </property> <property name="text"> - <string>Reset Dialogs</string> + <string>Reset Dialog Choices</string> </property> </widget> </item> diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 3f7ece38..8ecdcbb9 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -238,8 +238,11 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked() void GeneralSettingsTab::on_resetDialogsButton_clicked() { - if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"), - QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + if (QMessageBox::question( + parentWidget(), QObject::tr("Confirm?"), + QObject::tr( + "This will reset all the choices you made to dialogs and make them all " + "visible again. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { resetDialogs(); } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 0b08f13f..826075c0 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -226,7 +226,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { QDir(Settings::instance().paths().cache()).removeRecursively(); - NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); + NexusInterface::instance(dialog().pluginContainer())->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() @@ -238,7 +238,7 @@ void NexusSettingsTab::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager())); + *NexusInterface::instance(dialog().pluginContainer())->getAccessManager())); m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ onValidatorStateChanged(s, e); @@ -294,7 +294,7 @@ void NexusSettingsTab::onValidatorStateChanged( void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) { - NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user); + NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user); if (!user.apiKey().isEmpty()) { if (setKey(user.apiKey())) { @@ -311,7 +311,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().m_keyChanged = true; + dialog().setRestartNeeded(); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -319,10 +319,10 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().m_keyChanged = true; + dialog().setRestartNeeded(); const auto ret = settings().nexus().clearApiKey(); - NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); + NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); updateNexusState(); return ret; @@ -371,7 +371,7 @@ void NexusSettingsTab::updateNexusButtons() void NexusSettingsTab::updateNexusData() { - const auto user = NexusInterface::instance(dialog().m_PluginContainer) + const auto user = NexusInterface::instance(dialog().pluginContainer()) ->getAPIUserAccount(); if (user.isValid()) { diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index aeb4dd5d..c6fd40a7 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -68,10 +68,12 @@ void PathsSettingsTab::update() if (!QDir(realPath).exists()) { if (!QDir().mkpath(realPath)) { - QMessageBox::warning(qApp->activeWindow(), QObject::tr("Error"), + QMessageBox::warning(parentWidget(), QObject::tr("Error"), QObject::tr("Failed to create \"%1\", you may not have the " - "necessary permission. path remains unchanged.") + "necessary permissions. Path remains unchanged.") .arg(realPath)); + + continue; } } diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 4d811e40..0e31fc4b 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -1,6 +1,7 @@ #include "settingsdialogworkarounds.h" #include "ui_settingsdialog.h" -#include "helper.h" +#include "spawn.h" +#include "settings.h" #include <iplugingame.h> WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -26,7 +27,7 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) ui->lockGUIBox->setChecked(settings().interface().lockGUI()); ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); - setExecutableBlacklist(settings().executablesBlacklist()); + m_ExecutableBlacklist = settings().executablesBlacklist(); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); @@ -49,14 +50,29 @@ void WorkaroundsSettingsTab::update() settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); settings().interface().setLockGUI(ui->lockGUIBox->isChecked()); settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); - settings().setExecutablesBlacklist(getExecutableBlacklist()); + settings().setExecutablesBlacklist(m_ExecutableBlacklist); } -void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() +bool WorkaroundsSettingsTab::changeBlacklistNow( + QWidget* parent, Settings& settings) +{ + const auto current = settings.executablesBlacklist(); + + if (auto s=changeBlacklistLater(parent, current)) { + settings.setExecutablesBlacklist(*s); + return true; + } + + return false; +} + +std::optional<QString> WorkaroundsSettingsTab::changeBlacklistLater( + QWidget* parent, const QString& current) { bool ok = false; + QString result = QInputDialog::getMultiLineText( - &dialog(), + parent, QObject::tr("Executables Blacklist"), QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" "Mods and other virtualized files will not be visible to these executables and\n" @@ -64,17 +80,28 @@ void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() "Example:\n" " Chrome.exe\n" " Firefox.exe"), - m_ExecutableBlacklist.split(";").join("\n"), + current.split(";").join("\n"), &ok ); - if (ok) { - QStringList blacklist; - for (auto exec : result.split("\n")) { - if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { - blacklist << exec.trimmed(); - } + + if (!ok) { + return {}; + } + + QStringList blacklist; + for (auto exec : result.split("\n")) { + if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { + blacklist << exec.trimmed(); } - m_ExecutableBlacklist = blacklist.join(";"); + } + + return blacklist.join(";"); +} + +void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() +{ + if (auto s=changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) { + m_ExecutableBlacklist = *s; } } @@ -83,7 +110,9 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() const auto* game = qApp->property("managed_game").value<MOBase::IPluginGame*>(); QDir dir = game->dataDirectory(); - Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + helper::backdateBSAs( + parentWidget(), + qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); } @@ -95,7 +124,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() "Restart now?"); const auto res = QMessageBox::question( - nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); + parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); if (res == QMessageBox::Yes) { settings().geometry().requestReset(); diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h index d5d6815f..cffc54a0 100644 --- a/src/settingsdialogworkarounds.h +++ b/src/settingsdialogworkarounds.h @@ -8,6 +8,18 @@ class WorkaroundsSettingsTab : public SettingsTab { public: WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog); + + // shows the blacklist dialog from the given settings, and changes the + // settings when the user accepts it + // + static bool changeBlacklistNow(QWidget* parent, Settings& settings); + + // shows the blacklist dialog from the given string and returns the new + // blacklist if the user accepted it + // + static std::optional<QString> changeBlacklistLater( + QWidget* parent, const QString& current); + void update(); private: @@ -16,9 +28,6 @@ private: void on_bsaDateBtn_clicked(); void on_execBlacklistBtn_clicked(); void on_resetGeometryBtn_clicked(); - - QString getExecutableBlacklist() { return m_ExecutableBlacklist; } - void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } }; #endif // SETTINGSDIALOGWORKAROUNDS_H diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7a9dcc35..db7c1818 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -213,55 +213,119 @@ void warnIfNotCheckable(const QAbstractButton* b) } -bool setWindowsCredential(const QString key, const QString data) +QString credentialName(const QString& key) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); + return "ModOrganizer2_" + key; +} + +bool deleteWindowsCredential(const QString& key) +{ + const auto credName = credentialName(key); + + if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) { + const auto e = GetLastError(); - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + // not an error if the key already doesn't exist, and don't log it because + // it happens all the time when the settings dialog is closed since it + // doesn't check first + if (e == ERROR_NOT_FOUND) { + return true; + } - result = CredWriteW(&cred, 0); - delete[] charData; + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + return false; } - delete[] keyData; - return result; + + log::debug("deleted windows credential {}", credName); + + return true; } -QString getWindowsCredential(const QString key) +bool addWindowsCredential(const QString& key, const QString& data) { - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { + const auto credName = credentialName(key); + + const auto wname = credName.toStdWString(); + const auto wdata = data.toStdWString(); + + const auto* blob = reinterpret_cast<const BYTE*>(wdata.data()); + const auto blobSize = wdata.size() * sizeof(decltype(wdata)::value_type); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = const_cast<wchar_t*>(wname.c_str()); + cred.CredentialBlob = const_cast<BYTE*>(blob); + cred.CredentialBlobSize = static_cast<DWORD>(blobSize); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + if (!CredWriteW(&cred, 0)) { + const auto e = GetLastError(); + + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + + return false; + } + + log::debug("set windows credential {}", credName); + + return true; +} + +struct CredentialFreer +{ + void operator()(CREDENTIALW* c) + { + if (c) { + CredFree(c); + } + } +}; + +using CredentialPtr = std::unique_ptr<CREDENTIALW, CredentialFreer>; + +QString getWindowsCredential(const QString& key) +{ + const QString credName = credentialName(key); + + CREDENTIALW* rawCreds = nullptr; + + const auto ret = CredReadW( + credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds); + + CredentialPtr creds(rawCreds); + + if (!ret) { const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + log::error( + "failed to retrieve windows credential {}: {}", + credName, formatSystemMessage(e)); } + + return {}; + } + + QString value; + if (creds->CredentialBlob) { + value = QString::fromWCharArray( + reinterpret_cast<const wchar_t*>(creds->CredentialBlob), + creds->CredentialBlobSize / sizeof(wchar_t)); + } + + return value; +} + +bool setWindowsCredential(const QString& key, const QString& data) +{ + if (data.isEmpty()) { + return deleteWindowsCredential(key); + } else { + return addWindowsCredential(key, data); } - delete[] keyData; - return result; } diff --git a/src/settingsutilities.h b/src/settingsutilities.h index d99abb06..a6737144 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -3,7 +3,9 @@ #include <log.h> -class ExpanderWidget; +namespace MOBase { + class ExpanderWidget; +} template <class T, class=void> struct ValueConverter @@ -241,7 +243,7 @@ private: QString widgetNameWithTopLevel(const QWidget* widget); QString widgetName(const QMainWindow* w); QString widgetName(const QHeaderView* w); -QString widgetName(const ExpanderWidget* w); +QString widgetName(const MOBase::ExpanderWidget* w); QString widgetName(const QWidget* w); template <class Widget> @@ -268,7 +270,7 @@ QString checkedSettingName(const QAbstractButton* b); void warnIfNotCheckable(const QAbstractButton* b); -bool setWindowsCredential(const QString key, const QString data); -QString getWindowsCredential(const QString key); +bool setWindowsCredential(const QString& key, const QString& data); +QString getWindowsCredential(const QString& key); #endif // SETTINGSUTILITIES_H diff --git a/src/spawn.cpp b/src/spawn.cpp index f77da35f..079677f4 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -21,164 +21,878 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "report.h"
#include "utility.h"
+#include "env.h"
+#include "envwindows.h"
+#include "envsecurity.h"
+#include "envmodule.h"
+#include "settings.h"
+#include "settingsdialogworkarounds.h"
+#include <errorcodes.h>
#include <report.h>
+#include <log.h>
#include <usvfs.h>
#include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
-#include "helper.h"
-
#include <QApplication>
#include <QMessageBox>
#include <QtDebug>
-
-
#include <Shellapi.h>
-
-#include <boost/scoped_array.hpp>
+#include <fmt/format.h>
using namespace MOBase;
using namespace MOShared;
+namespace spawn::dialogs
+{
+
+std::wstring makeRightsDetails(const env::FileSecurity& fs)
+{
+ if (fs.rights.normalRights) {
+ return L"(normal rights)";
+ }
+
+ if (fs.rights.list.isEmpty()) {
+ return L"(none)";
+ }
+
+ std::wstring s = fs.rights.list.join("|").toStdWString();
+ if (!fs.rights.hasExecute) {
+ s += L" (execute is missing)";
+ }
+
+ return s;
+}
+
+QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more={})
+{
+ std::wstring owner, rights;
+
+ if (sp.binary.isFile()) {
+ const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath());
+
+ if (fs.error.isEmpty()) {
+ owner = fs.owner.toStdWString();
+ rights = makeRightsDetails(fs);
+ } else {
+ owner = fs.error.toStdWString();
+ rights = fs.error.toStdWString();
+ }
+ } else {
+ owner = L"(file not found)";
+ rights = L"(file not found)";
+ }
+
+ const bool cwdExists = (sp.currentDirectory.isEmpty() ?
+ true : sp.currentDirectory.exists());
+
+ const auto appDir = QCoreApplication::applicationDirPath();
+ const auto sep = QDir::separator();
+
+ const std::wstring usvfs_x86_dll =
+ QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_dll =
+ QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x86_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found";
+
+ std::wstring elevated;
+ if (auto b=env::Environment().windowsInfo().isElevated()) {
+ elevated = (*b ? L"yes" : L"no");
+ } else {
+ elevated = L"(not available)";
+ }
+
+ std::wstring f =
+ L"Error {code} {codename}{more}: {error}\n"
+ L" . binary: '{bin}'\n"
+ L" . owner: {owner}\n"
+ L" . rights: {rights}\n"
+ L" . arguments: '{args}'\n"
+ L" . cwd: '{cwd}'{cwdexists}\n"
+ L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n"
+ L" . MO elevated: {elevated}";
+
+ if (sp.hooked) {
+ f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}";
+ }
+
+ const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString());
+
+ const auto s = fmt::format(f,
+ fmt::arg(L"code", code),
+ fmt::arg(L"codename", errorCodeName(code)),
+ fmt::arg(L"more", wmore),
+ fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()),
+ fmt::arg(L"owner", owner),
+ fmt::arg(L"rights", rights),
+ fmt::arg(L"error", formatSystemMessage(code)),
+ fmt::arg(L"args", sp.arguments.toStdWString()),
+ fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()),
+ fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")),
+ fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")),
+ fmt::arg(L"x86_dll", usvfs_x86_dll),
+ fmt::arg(L"x64_dll", usvfs_x64_dll),
+ fmt::arg(L"x86_proxy", usvfs_x86_proxy),
+ fmt::arg(L"x64_proxy", usvfs_x64_proxy),
+ fmt::arg(L"elevated", elevated));
+
+ return QString::fromStdWString(s);
+}
+
+QString makeContent(const SpawnParameters& sp, DWORD code)
+{
+ if (code == ERROR_INVALID_PARAMETER) {
+ return QObject::tr(
+ "This error typically happens because an antivirus has deleted critical "
+ "files from Mod Organizer's installation folder or has made them "
+ "generally inaccessible. Add an exclusion for Mod Organizer's "
+ "installation folder in your antivirus, reinstall Mod Organizer and try "
+ "again.");
+ } else if (code == ERROR_ACCESS_DENIED) {
+ return QObject::tr(
+ "This error typically happens because an antivirus is preventing Mod "
+ "Organizer from starting programs. Add an exclusion for Mod Organizer's "
+ "installation folder in your antivirus and try again.");
+ } else if (code == ERROR_FILE_NOT_FOUND) {
+ return QObject::tr("The file '%1' does not exist.")
+ .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath()));
+ } else {
+ return QString::fromStdWString(formatSystemMessage(code));
+ }
+}
+
+QMessageBox::StandardButton badSteamReg(
+ QWidget* parent, const QString& keyName, const QString& valueName)
+{
+ const auto details = QString(
+ "can't start steam, registry value at '%1' is empty or doesn't exist")
+ .arg(keyName + "\\" + valueName);
+
+ log::error("{}", details);
+
+ return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam"))
+ .main(QObject::tr("Cannot start Steam"))
+ .content(QObject::tr(
+ "The path to the Steam executable cannot be found. You might try "
+ "reinstalling Steam."))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program may fail to launch."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+}
+
+QMessageBox::StandardButton startSteamFailed(
+ QWidget* parent,
+ const QString& keyName, const QString& valueName, const QString& exe,
+ const SpawnParameters& sp, DWORD e)
+{
+ auto details = QString(
+ "a steam install was found in the registry at '%1': '%2'\n\n")
+ .arg(keyName + "\\" + valueName)
+ .arg(exe);
+
+ details += makeDetails(sp, e);
+
+ log::error("{}", details);
+
+ return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam"))
+ .main(QObject::tr("Cannot start Steam"))
+ .content(makeContent(sp, e))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program may fail to launch."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+}
+
+void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code)
+{
+ const auto details = makeDetails(sp, code);
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Cannot launch program");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(makeContent(sp, code))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .exec();
+}
+
+void helperFailed(
+ QWidget* parent, DWORD code, const QString& why, const std::wstring& binary,
+ const std::wstring& cwd, const std::wstring& args)
+{
+ SpawnParameters sp;
+ sp.binary = QString::fromStdWString(binary);
+ sp.currentDirectory.setPath(QString::fromStdWString(cwd));
+ sp.arguments = QString::fromStdWString(args);
+
+ const auto details = makeDetails(sp, code, "in " + why);
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Cannot launch helper");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(makeContent(sp, code))
+ .details(details)
+ .icon(QMessageBox::Critical)
+ .exec();
+}
+
+bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp)
+{
+ const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED);
+
+ log::error("{}", details);
+
+ const auto title = QObject::tr("Elevation required");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(sp.binary.fileName());
+
+ const auto content = QObject::tr(
+ "This program is requesting to run as administrator but Mod Organizer "
+ "itself is not running as administrator. Running programs as administrator "
+ "is typically unnecessary as long as the game and Mod Organizer have been "
+ "installed outside \"Program Files\".\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+ log::debug("asking user to restart MO as administrator");
+
+ const auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Restart Mod Organizer as administrator"),
+ QObject::tr("You must allow \"helper.exe\" to make changes to the system."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Yes);
+}
+
+QMessageBox::StandardButton confirmStartSteam(
+ QWidget* parent, const SpawnParameters& sp, const QString& details)
+{
+ const auto title = QObject::tr("Launch Steam");
+ const auto mainText = QObject::tr("This program requires Steam");
+ const auto content = QObject::tr(
+ "Mod Organizer has detected that this program likely requires Steam to be "
+ "running to function properly.");
+
+ return MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Start Steam"),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamQuery", sp.binary.fileName())
+ .exec();
+}
+
+QMessageBox::StandardButton confirmRestartAsAdminForSteam(
+ QWidget* parent, const SpawnParameters& sp)
+{
+ const auto title = QObject::tr("Elevation required");
+ const auto mainText = QObject::tr("Steam is running as administrator");
+ const auto content = QObject::tr(
+ "Running Steam as administrator is typically unnecessary and can cause "
+ "problems when Mod Organizer itself is not running as administrator."
+ "\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+ return MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Restart Mod Organizer as administrator"),
+ QObject::tr("You must allow \"helper.exe\" to make changes to the system."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamAdminQuery", sp.binary.fileName())
+ .exec();
+}
-static const int BUFSIZE = 4096;
+bool eventLogNotRunning(
+ QWidget* parent, const env::Service& s, const SpawnParameters& sp)
+{
+ const auto title = QObject::tr("Event Log not running");
+ const auto mainText = QObject::tr("The Event Log service is not running");
+ const auto content = QObject::tr(
+ "The Windows Event Log service is not running. This can prevent USVFS from "
+ "running properly and your mods may not be recognized by the program being "
+ "launched.");
+
+ const auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(s.toString())
+ .icon(QMessageBox::Question)
+ .remember("eventLogService", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Yes);
+}
+
+QMessageBox::StandardButton confirmBlacklisted(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
+{
+ const auto title = QObject::tr("Blacklisted program");
+ const auto mainText = QObject::tr("The program %1 is blacklisted")
+ .arg(sp.binary.fileName());
+ const auto content = QObject::tr(
+ "The program you are attempting to launch is blacklisted in the virtual "
+ "filesystem. This will likely prevent it from seeing any mods, INI files "
+ "or any other virtualized files.");
+
+ const auto details =
+ "Executable: " + sp.binary.fileName() + "\n"
+ "Current blacklist: " + settings.executablesBlacklist();
+
+ auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .remember("blacklistedExecutable", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Change the blacklist"),
+ QMessageBox::Retry})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ if (r == QMessageBox::Retry) {
+ if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) {
+ r = QMessageBox::Cancel;
+ }
+ }
+
+ return r;
+}
+
+} // namespace
+
+
+namespace spawn
+{
-static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory,
- bool suspended, bool hooked,
- HANDLE stdOut, HANDLE stdErr,
- HANDLE& processHandle, HANDLE& threadHandle)
+DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle)
{
BOOL inheritHandles = FALSE;
- STARTUPINFO si;
- ::ZeroMemory(&si, sizeof(si));
- if (stdOut != INVALID_HANDLE_VALUE) {
- si.hStdOutput = stdOut;
+
+ STARTUPINFO si = {};
+ si.cb = sizeof(si);
+
+ // inherit handles if we plan to use stdout or stderr reroute
+ if (sp.stdOut != INVALID_HANDLE_VALUE) {
+ si.hStdOutput = sp.stdOut;
inheritHandles = TRUE;
si.dwFlags |= STARTF_USESTDHANDLES;
}
- if (stdErr != INVALID_HANDLE_VALUE) {
- si.hStdError = stdErr;
+
+ if (sp.stdErr != INVALID_HANDLE_VALUE) {
+ si.hStdError = sp.stdErr;
inheritHandles = TRUE;
si.dwFlags |= STARTF_USESTDHANDLES;
}
- si.cb = sizeof(si);
- size_t length = wcslen(binary) + wcslen(arguments) + 4;
- wchar_t *commandLine = nullptr;
- if (arguments[0] != L'\0') {
- commandLine = new wchar_t[length];
- _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments);
- } else {
- commandLine = new wchar_t[length];
- _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary);
+
+ const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString();
+ const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString();
+
+ std::wstring commandLine = L"\"" + bin + L"\"";
+ if (sp.arguments[0] != L'\0') {
+ commandLine += L" " + sp.arguments.toStdWString();
}
QString moPath = QCoreApplication::applicationDirPath();
+ const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath));
+
+ PROCESS_INFORMATION pi;
+ BOOL success = FALSE;
+
+ if (sp.hooked) {
+ success = ::CreateProcessHooked(
+ nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ cwd.c_str(), &si, &pi);
+ } else {
+ success = ::CreateProcess(
+ nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ cwd.c_str(), &si, &pi);
+ }
+
+ const auto e = GetLastError();
+ env::setPath(oldPath);
+
+ if (!success) {
+ return e;
+ }
- boost::scoped_array<TCHAR> oldPath(new TCHAR[BUFSIZE]);
- DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE);
- if (offset > BUFSIZE) {
- oldPath.reset(new TCHAR[offset]);
- ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset);
+ processHandle = pi.hProcess;
+ threadHandle = pi.hThread;
+
+ return ERROR_SUCCESS;
+}
+
+bool restartAsAdmin(QWidget* parent)
+{
+ WCHAR cwd[MAX_PATH] = {};
+ if (!GetCurrentDirectory(MAX_PATH, cwd)) {
+ cwd[0] = L'\0';
}
+ if (!helper::adminLaunch(
+ parent,
+ qApp->applicationDirPath().toStdWString(),
+ qApp->applicationFilePath().toStdWString(),
+ std::wstring(cwd)))
{
- boost::scoped_array<TCHAR> newPath(new TCHAR[offset + moPath.length() + 2]);
- _tcsncpy(newPath.get(), oldPath.get(), offset);
- newPath.get()[offset] = '\0';
- _tcsncat(newPath.get(), TEXT(";"), 1);
- _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length());
+ log::error("admin launch failed");
+ return false;
+ }
+
+ log::debug("exiting MO");
+ qApp->exit(0);
- ::SetEnvironmentVariable(TEXT("PATH"), newPath.get());
+ return true;
+}
+
+void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp)
+{
+ if (!dialogs::confirmRestartAsAdmin(parent, sp)) {
+ log::debug("user declined");
+ return;
}
- PROCESS_INFORMATION pi;
- BOOL success = FALSE;
- if (hooked) {
- success = ::CreateProcessHooked(nullptr,
- commandLine,
- nullptr, nullptr, // no special process or thread attributes
- inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
- CREATE_BREAKAWAY_FROM_JOB,
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+ log::info("restarting MO as administrator");
+ restartAsAdmin(parent);
+}
+
+bool checkBinary(QWidget* parent, const SpawnParameters& sp)
+{
+ if (!sp.binary.exists()) {
+ dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND);
+ return false;
+ }
+
+ return true;
+}
+
+struct SteamStatus
+{
+ bool running=false;
+ bool accessible=false;
+};
+
+SteamStatus getSteamStatus()
+{
+ SteamStatus ss;
+
+ const auto ps = env::Environment().runningProcesses();
+
+ for (const auto& p : ps) {
+ if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) ||
+ (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0))
+ {
+ ss.running = true;
+ ss.accessible = p.canAccess();
+
+ log::debug(
+ "'{}' is running, accessible={}",
+ p.name(), (ss.accessible ? "yes" : "no"));
+
+ break;
+ }
+ }
+
+ return ss;
+}
+
+QString makeSteamArguments(const QString& username, const QString& password)
+{
+ QString args;
+
+ if (username != "") {
+ args += "-login " + username;
+
+ if (password != "") {
+ args += " " + password;
+ }
+ }
+
+ return args;
+}
+
+bool startSteam(QWidget* parent)
+{
+ const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam";
+ const QString valueName = "SteamExe";
+
+ const QSettings steamSettings(keyName, QSettings::NativeFormat);
+ const QString exe = steamSettings.value(valueName, "").toString();
+
+ if (exe.isEmpty()) {
+ return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes);
+ }
+
+ SpawnParameters sp;
+ sp.binary = exe;
+
+ // See if username and password supplied. If so, pass them into steam.
+ QString username, password;
+ if (Settings::instance().steam().login(username, password)) {
+ sp.arguments = makeSteamArguments(username, password);
+ }
+
+ log::debug(
+ "starting steam process:\n"
+ " . program: '{}'\n"
+ " . username={}, password={}",
+ sp.binary.filePath().toStdString(),
+ (username.isEmpty() ? "no" : "yes"),
+ (password.isEmpty() ? "no" : "yes"));
+
+ HANDLE ph = INVALID_HANDLE_VALUE;
+ HANDLE th = INVALID_HANDLE_VALUE;
+ const auto e = spawn(sp, ph, th);
+
+ if (e != ERROR_SUCCESS) {
+ // make sure username and passwords are not shown
+ sp.arguments = makeSteamArguments(
+ (username.isEmpty() ? "" : "USERNAME"),
+ (password.isEmpty() ? "" : "PASSWORD"));
+
+ const auto r = dialogs::startSteamFailed(
+ parent, keyName, valueName, exe, sp, e);
+
+ return (r == QMessageBox::Yes);
+ }
+
+ QMessageBox::information(
+ parent, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
+
+ return true;
+}
+
+bool checkSteam(
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings)
+{
+ static const std::vector<QString> steamFiles = {
+ "steam_api.dll", "steam_api64.dll"
+ };
+
+ log::debug("checking steam");
+
+ if (!steamAppID.isEmpty()) {
+ env::set("SteamAPPId", steamAppID);
} else {
- success = ::CreateProcess(nullptr,
- commandLine,
- nullptr, nullptr, // no special process or thread attributes
- inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
- CREATE_BREAKAWAY_FROM_JOB,
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+ env::set("SteamAPPId", settings.steam().appID());
}
- ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get());
- delete [] commandLine;
+ bool steamRequired = false;
+ QString details;
- if (!success) {
- throw windows_error("failed to start process");
+ for (const auto& file : steamFiles) {
+ const QFileInfo fi(gameDirectory.absoluteFilePath(file));
+ if (fi.exists()) {
+ details = QString(
+ "managed game is located at '%1' and file '%2' exists")
+ .arg(gameDirectory.absolutePath())
+ .arg(fi.absoluteFilePath());
+
+ log::debug("{}", details);
+ steamRequired = true;
+
+ break;
+ }
+ }
+
+ if (!steamRequired) {
+ log::debug("program doesn't seem to require steam");
+ return true;
+ }
+
+
+ auto ss = getSteamStatus();
+
+ if (!ss.running) {
+ log::debug("steam isn't running, asking to start steam");
+
+ const auto c = dialogs::confirmStartSteam(parent, sp, details);
+
+ if (c == QDialogButtonBox::Yes) {
+ log::debug("user wants to start steam");
+
+ if (!startSteam(parent)) {
+ // cancel
+ return false;
+ }
+
+ // double-check that Steam is started
+ ss = getSteamStatus();
+ if (!ss.running) {
+ log::error("steam is still not running, hoping for the best");
+ return true;
+ }
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to start steam");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
+ }
+ }
+
+ if (ss.running && !ss.accessible) {
+ log::debug("steam is running but is not accessible, asking to restart MO");
+ const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp);
+
+ if (c == QDialogButtonBox::Yes) {
+ restartAsAdmin(parent);
+ return false;
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to restart MO, continuing");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
+ }
}
- processHandle = pi.hProcess;
- threadHandle = pi.hThread;
return true;
}
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
+{
+ // check if the Windows Event Logging service is running; for some reason,
+ // this seems to be critical to the successful running of usvfs.
+ const auto serviceName = "EventLog";
+
+ const auto s = env::getService(serviceName);
+
+ if (!s.isValid()) {
+ log::error(
+ "cannot determine the status of the {} service, continuing",
+ serviceName);
+
+ return true;
+ }
+
+ if (s.status() == env::Service::Status::Running) {
+ log::debug("{}", s.toString());
+ return true;
+ }
+
+ log::error("{}", s.toString());
+ return dialogs::eventLogNotRunning(parent, s, sp);
+}
+
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
+{
+ for (;;) {
+ if (!settings.isExecutableBlacklisted(sp.binary.fileName())) {
+ return true;
+ }
+
+ const auto r = dialogs::confirmBlacklisted(parent, sp, settings);
+
+ if (r != QMessageBox::Retry) {
+ return (r == QMessageBox::Yes);
+ }
+ }
+}
+
-HANDLE startBinary(const QFileInfo &binary,
- const QString &arguments,
- const QDir ¤tDirectory,
- bool hooked,
- HANDLE stdOut,
- HANDLE stdErr)
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
HANDLE processHandle, threadHandle;
- std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
- std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
+ const auto e = spawn(sp, processHandle, threadHandle);
- try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(),
- true, hooked, stdOut, stdErr, processHandle, threadHandle)) {
- reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
+ switch (e)
+ {
+ case ERROR_SUCCESS:
+ {
+ ::CloseHandle(threadHandle);
+ return processHandle;
}
- } catch (const windows_error &e) {
- if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
- if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"),
- QObject::tr("This process requires elevation to run.\n"
- "This is a potential security risk so I highly advise you to investigate if\n"
- "\"%1\"\n"
- "can be installed to work without elevation.\n\n"
- "Restart Mod Organizer as an elevated process?\n"
- "You will be asked if you want to allow helper.exe to make changes to the system. "
- "You will need to relaunch the process above manually.").arg(
- QDir::toNativeSeparators(binary.absoluteFilePath())),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(::GetLastError()));
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- return INVALID_HANDLE_VALUE;
- }
- qApp->exit(0);
- }
+
+ case ERROR_ELEVATION_REQUIRED:
+ {
+ startBinaryAdmin(parent, sp);
return INVALID_HANDLE_VALUE;
+ }
- } else {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what()));
+ default:
+ {
+ dialogs::spawnFailed(parent, sp, e);
return INVALID_HANDLE_VALUE;
}
}
+}
+
+} // namespace
+
+
+
+namespace helper
+{
- ::CloseHandle(threadHandle);
- return processHandle;
+bool helperExec(
+ QWidget* parent,
+ const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async)
+{
+ const std::wstring fileName = moDirectory + L"\\helper.exe";
+
+ env::HandlePtr process;
+
+ {
+ SHELLEXECUTEINFOW execInfo = {};
+
+ ULONG flags = SEE_MASK_FLAG_NO_UI ;
+ if (!async)
+ flags |= SEE_MASK_NOCLOSEPROCESS;
+
+ execInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ execInfo.fMask = flags;
+ execInfo.hwnd = 0;
+ execInfo.lpVerb = L"runas";
+ execInfo.lpFile = fileName.c_str();
+ execInfo.lpParameters = commandLine.c_str();
+ execInfo.lpDirectory = moDirectory.c_str();
+ execInfo.nShow = SW_SHOW;
+
+ if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) {
+ const auto e = GetLastError();
+
+ spawn::dialogs::helperFailed(
+ parent, e, "ShellExecuteExW()", fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ if (async) {
+ return true;
+ }
+
+ process.reset(execInfo.hProcess);
+ }
+
+ const auto r = ::WaitForSingleObject(process.get(), INFINITE);
+
+ if (r != WAIT_OBJECT_0) {
+ // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError()
+ // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so
+ // use that instead
+ const auto code = (r == WAIT_ABANDONED ?
+ ERROR_ABANDONED_WAIT_0 : GetLastError());
+
+ spawn::dialogs::helperFailed(
+ parent, code, "WaitForSingleObject()",
+ fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ DWORD exitCode = 0;
+ if (!GetExitCodeProcess(process.get(), &exitCode)) {
+ const auto e = GetLastError();
+
+ spawn::dialogs::helperFailed(
+ parent, e, "GetExitCodeProcess()", fileName, moDirectory, commandLine);
+
+ return false;
+ }
+
+ return (exitCode == 0);
+}
+
+bool backdateBSAs(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath)
+{
+ const std::wstring commandLine = fmt::format(
+ L"backdateBSA \"{}\"", dataPath);
+
+ return helperExec(parent, moPath, commandLine, FALSE);
+}
+
+bool adminLaunch(
+ QWidget* parent, const std::wstring &moPath,
+ const std::wstring &moFile, const std::wstring &workingDir)
+{
+ const std::wstring commandLine = fmt::format(
+ L"adminLaunch {} \"{}\" \"{}\"",
+ ::GetCurrentProcessId(), moFile, workingDir);
+
+ return helperExec(parent, moPath, commandLine, true);
}
+
+} // namespace
diff --git a/src/spawn.h b/src/spawn.h index c2d99bdb..31b44739 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -26,28 +26,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QFileInfo>
#include <QDir>
+class Settings;
-/**
- * @brief a dirty little trick so we can issue a clean restart from startBinary
- * @note unused
- */
-/*class ExitProxy : public QObject {
- Q_OBJECT
-public:
- static ExitProxy *instance();
- void emitExit();
-signals:
- void exit();
-private:
- ExitProxy() {}
-private:
- static ExitProxy *s_Instance;
-};*/
-
+namespace spawn
+{
-/**
- * @brief spawn a binary with Mod Organizer injected
- *
+/*
* @param binary the binary to spawn
* @param arguments arguments to pass to the binary
* @param profileName name of the active profile
@@ -56,13 +40,64 @@ private: * @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
+*/
+struct SpawnParameters
+{
+ QFileInfo binary;
+ QString arguments;
+ QDir currentDirectory;
+ bool hooked = false;
+ HANDLE stdOut = INVALID_HANDLE_VALUE;
+ HANDLE stdErr = INVALID_HANDLE_VALUE;
+};
+
+
+bool checkBinary(QWidget* parent, const SpawnParameters& sp);
+
+bool checkSteam(
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings);
+
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp);
+
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings);
+
+/**
+ * @brief spawn a binary with Mod Organizer injected
* @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 QDir ¤tDirectory, bool hooked,
- HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
+
+} // namespace
+
+
+// convenience functions to work with the external helper program, which is used
+// to make changes on the system that require administrative rights, so that
+// ModOrganizer itself can run without special privileges
+//
+namespace helper
+{
+
+/**
+* @brief sets the last modified time for all .bsa-files in the target directory well into the past
+* @param moPath absolute path to the modOrganizer base directory
+* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game
+**/
+bool backdateBSAs(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath);
+
+/**
+* @brief waits for the current process to exit and restarts it as an administrator
+* @param moPath absolute path to the modOrganizer base directory
+* @param moFile file name of modOrganizer
+* @param workingDir current working directory
+**/
+bool adminLaunch(
+ QWidget* parent, const std::wstring &moPath,
+ const std::wstring &moFile, const std::wstring &workingDir);
+
+} // namespace
#endif // SPAWN_H
|
