diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp
index 73dec01..b5656a3 100644
--- a/src/proxy/proxypython.cpp
+++ b/src/proxy/proxypython.cpp
@@ -26,172 +26,86 @@ along with python proxy plugin. If not, see .
#include
#include
#include
-#include "resource.h"
+#include "log.h"
using namespace MOBase;
-
-const char *ProxyPython::s_DownloadPythonURL = "http://www.python.org/download/releases/";
-
-
-HMODULE GetOwnModuleHandle()
-{
- HMODULE hMod = nullptr;
- GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
- reinterpret_cast(&GetOwnModuleHandle), &hMod);
-
- return hMod;
-}
-
-
-QString ExtractResource(WORD resourceID, const QString &szFilename)
-{
- HMODULE mod = GetOwnModuleHandle();
-
- HRSRC hResource = FindResourceW(mod, MAKEINTRESOURCE(resourceID), L"BINARY");
- if (hResource == nullptr) {
- throw Exception("embedded dll not available: " + windowsErrorString(::GetLastError()));
- }
-
- HGLOBAL hFileResource = LoadResource(mod, hResource);
- if (hFileResource == nullptr) {
- throw Exception("failed to load embedded dll resource: " + windowsErrorString(::GetLastError()));
- }
-
- LPVOID lpFile = LockResource(hFileResource);
- if (lpFile == nullptr) {
- throw Exception(QString("failed to lock resource: %1").arg(windowsErrorString(::GetLastError())));
- }
-
- DWORD dwSize = SizeofResource(mod, hResource);
-
- QString outFile = QDir::tempPath() + "/" + szFilename;
-
- HANDLE hFile = CreateFileW(outFile.toStdWString().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
- if (hFile == INVALID_HANDLE_VALUE) {
- if (::GetLastError() == ERROR_SHARING_VIOLATION) {
- // dll exists and is opened by another instance of MO, shouldn't be outdated then...
- return outFile;
- } else {
- throw Exception(QString("failed to open python runner: %1").arg(windowsErrorString(::GetLastError())));
- }
- }
- HANDLE hFileMap = CreateFileMapping(hFile, nullptr, PAGE_READWRITE, 0, dwSize, nullptr);
- if (hFileMap == NULL) {
- throw Exception(QString("failed to map python runner: %1").arg(windowsErrorString(::GetLastError())));
- }
- LPVOID lpAddress = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);
- if (lpAddress == nullptr) {
- throw Exception(QString("failed to map view of file: %1").arg(windowsErrorString(::GetLastError())));
- }
-
- CopyMemory(lpAddress, lpFile, dwSize);
-
- UnmapViewOfFile(lpAddress);
-
- CloseHandle(hFileMap);
- ::FlushFileBuffers(hFile);
- CloseHandle(hFile);
-
- return outFile;
-}
-
-
ProxyPython::ProxyPython()
- : m_MOInfo(nullptr), m_Runner(nullptr), m_LoadFailure(FAIL_NOTINIT)
+ : m_MOInfo{ nullptr },
+ m_RunnerLib{ nullptr },
+ m_Runner{ nullptr },
+ m_LoadFailure(FailureType::NONE)
{
}
-ProxyPython::~ProxyPython()
-{
- delete m_Runner;
-
- if (!m_TempRunnerFile.isEmpty()) {
- ::FreeLibrary(m_RunnerLib);
- QFile(m_TempRunnerFile).remove();
- }
-}
-
-typedef IPythonRunner* (*CreatePythonRunner_func)(const QString &pythonPath);
-
bool ProxyPython::init(IOrganizer *moInfo)
{
+ using CreatePythonRunner_func = IPythonRunner * (*)();
+
m_MOInfo = moInfo;
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
- m_LoadFailure = FAIL_NONE;
return false;
}
- m_LoadFailure = FAIL_OTHER;
if (QCoreApplication::applicationDirPath().contains(';')) {
- m_LoadFailure = FAIL_SEMICOLON;
+ m_LoadFailure = FailureType::SEMICOLON;
return true;
}
- QString pythonPath;
+ // load the pythonrunner library
+ m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(
+ IOrganizer::getPluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
- if (m_MOInfo) {
- pythonPath = m_MOInfo->pluginSetting(name(), "python_dir").toString();
+ if (!m_RunnerLib) {
+ DWORD error = ::GetLastError();
+ log::error("failed to load python runner ({}): {}", qUtf8Printable(windowsErrorString(error)));
+ if (error == ERROR_MOD_NOT_FOUND) {
+ m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ }
+ else {
+ m_LoadFailure = FailureType::INVALID_DLL;
+ }
+ return true;
+ }
- if (!pythonPath.isEmpty() && !QFile::exists(pythonPath + "/python.exe")) {
- m_LoadFailure = FAIL_WRONGPYTHONPATH;
+ const CreatePythonRunner_func createPythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
+ if (!createPythonRunner) {
+ m_LoadFailure = FailureType::INVALID_DLL;
+ return true;
+ }
+
+ if (m_MOInfo && m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
+ m_LoadFailure = FailureType::INITIALIZATION;
+ if (QMessageBox::question(parentWidget(), tr("Python Initialization failed"),
+ tr("On a previous start the Python Plugin failed to initialize.\n"
+ "Do you want to try initializing python again (at the risk of another crash)?\n"
+ "Suggestion: Select \"no\", and click the warning sign for further help. Afterwards you have to re-enable the python plugin."),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
+
+ // we force enabled here (note: this is a persistent settings since MO2 2.4 or something), plugin
+ // usually should not handle enabled/disabled themselves but this is a base plugin so...
+ m_MOInfo->setPersistent(name(), "enabled", false, true);
return true;
}
}
- m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(
- IOrganizer::getPluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
-
- if (m_RunnerLib != nullptr) {
- CreatePythonRunner_func CreatePythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
- if (CreatePythonRunner == nullptr) {
- throw Exception("embedded dll is invalid: " + windowsErrorString(::GetLastError()));
- }
-
- if (m_MOInfo && m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
- if (pythonPath.isEmpty()) {
- m_LoadFailure = FAIL_PYTHONDETECTION;
- } else {
- m_LoadFailure = FAIL_WRONGPYTHONPATH;
- }
- if (QMessageBox::question(parentWidget(), tr("Python Initialization failed"),
- tr("On a previous start the Python Plugin failed to initialize.\n"
- "Either the value in Settings->Plugins->ProxyPython->plugin_dir is set incorrectly or it is empty and auto-detection doesn't work "
- "for whatever reason.\n"
- "Do you want to try initializing python again (at the risk of another crash)?\n"
- "Suggestion: Select \"no\", and click the warning sign for further help. Afterwards you have to re-enable the python plugin."),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
- m_MOInfo->setPluginSetting(name(), "enabled", false);
- return true;
- }
- }
-
- if (m_MOInfo) {
- m_MOInfo->setPersistent(name(), "tryInit", true);
- }
-
- m_Runner = CreatePythonRunner(pythonPath);
-
- if (m_MOInfo) {
- m_MOInfo->setPersistent(name(), "tryInit", false);
- }
-
- if (m_Runner != nullptr) {
- m_LoadFailure = FAIL_NONE;
- } else {
- m_LoadFailure = FAIL_INITFAIL;
- }
- return true;
- } else {
- DWORD error = ::GetLastError();
- qCritical("Failed to load python runner (%s): %s", qUtf8Printable(m_TempRunnerFile), qUtf8Printable(windowsErrorString(error)));
- if (error == ERROR_MOD_NOT_FOUND) {
- m_LoadFailure = FAIL_MISSINGDEPENDENCIES;
- }
- return true;
+ if (m_MOInfo) {
+ m_MOInfo->setPersistent(name(), "tryInit", true);
}
+
+ m_Runner = std::unique_ptr{ createPythonRunner() };
+
+ if (m_MOInfo) {
+ m_MOInfo->setPersistent(name(), "tryInit", false);
+ }
+
+ if (!m_Runner) {
+ m_LoadFailure = FailureType::INITIALIZATION;
+ }
+
+ return true;
}
QString ProxyPython::name() const
@@ -216,15 +130,12 @@ QString ProxyPython::description() const
VersionInfo ProxyPython::version() const
{
- return VersionInfo(2, 2, 0, VersionInfo::RELEASE_FINAL);
+ return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
}
QList ProxyPython::settings() const
{
- QList result;
- result.push_back(PluginSetting("python_dir", "Path to your python installation. Leave empty for auto-detection", ""));
- result.push_back(PluginSetting("enabled", "Set to true to enable support for python plugins", true));
- return result;
+ return {};
}
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
@@ -269,103 +180,63 @@ void ProxyPython::unload(const QString& identifier)
std::vector ProxyPython::activeProblems() const
{
- std::vector result;
- if (m_LoadFailure == FAIL_MISSINGDEPENDENCIES) {
- result.push_back(PROBLEM_PYTHONMISSING);
- } else if (m_LoadFailure == FAIL_WRONGPYTHONPATH) {
- result.push_back(PROBLEM_WRONGPYTHONPATH);
- } else if (m_LoadFailure == FAIL_PYTHONDETECTION) {
- result.push_back(PROBLEM_PYTHONDETECTION);
- } else if (m_LoadFailure == FAIL_INITFAIL) {
- result.push_back(PROBLEM_INITFAIL);
- } else if (m_LoadFailure == FAIL_SEMICOLON) {
- result.push_back(PROBLEM_SEMICOLON);
- } else if (m_Runner != nullptr) {
- if (!m_Runner->isPythonInstalled()) {
- // don't know how this could happen but wth
- result.push_back(PROBLEM_PYTHONMISSING);
- }
- if (!m_Runner->isPythonVersionSupported()) {
- result.push_back(PROBLEM_PYTHONWRONGVERSION);
- }
+ auto failure = m_LoadFailure;
+
+ // don't know how this could happen but wth
+ if (m_Runner && !m_Runner->isPythonInitialized()) {
+ failure = FailureType::INITIALIZATION;
}
- return result;
+ if (failure != FailureType::NONE) {
+ return { static_cast>(failure) };
+ }
+
+ return {};
}
QString ProxyPython::shortDescription(unsigned int key) const
{
- switch (key) {
- case PROBLEM_PYTHONMISSING: {
- return tr("Python not installed or not found");
- } break;
- case PROBLEM_PYTHONWRONGVERSION: {
- return tr("Python version is incompatible");
- } break;
- case PROBLEM_WRONGPYTHONPATH: {
- return tr("Invalid python path");
- } break;
- case PROBLEM_INITFAIL: {
- return tr("Initializing Python failed");
- } break;
- case PROBLEM_PYTHONDETECTION: {
- return tr("Python auto-detection failed");
- } break;
- case PROBLEM_SEMICOLON: {
+ switch (static_cast(key)) {
+ case FailureType::SEMICOLON:
return tr("ModOrganizer path contains a semicolon");
- } break;
+ case FailureType::DLL_NOT_FOUND:
+ return tr("Python DLL not found");
+ case FailureType::INVALID_DLL:
+ return tr("Invalid Python DLL");
+ case FailureType::INITIALIZATION:
+ return tr("Initializing Python failed");
default:
- throw Exception(tr("invalid problem key %1").arg(key));
+ return tr("invalid problem key %1").arg(key);
}
}
QString ProxyPython::fullDescription(unsigned int key) const
{
- switch (key) {
- case PROBLEM_PYTHONMISSING: {
- return tr("Some MO plugins require the python interpreter to be installed. "
- "These plugins will not even show up in settings->plugins.
"
- "If you want to use those plugins, please install the 32-bit version of Python 2.7.x from %1.
"
- "This is only required to use some extended functionality in MO, you do not need Python to play the game.").arg(s_DownloadPythonURL);
- } break;
- case PROBLEM_PYTHONWRONGVERSION: {
- return tr("Your installed python version has a different version than 2.7. "
- "Some MO plugins may not work.
"
- "If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) "
- "in the settings dialog.
"
- "This is only required to use some extended functionality in MO, you do not need Python to play the game.");
- } break;
- case PROBLEM_WRONGPYTHONPATH: {
- return tr("Please set python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.");
- } break;
- case PROBLEM_PYTHONDETECTION: {
- return tr("The auto-detection of the python path failed. I don't know why this would happen but you can try to fix it "
- "by setting python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.");
- } break;
- case PROBLEM_INITFAIL: {
- return tr("Sorry, I don't know any details. Most likely your python installation is not supported.");
- } break;
- case PROBLEM_SEMICOLON: {
- return tr("The path to Mod Organizer (%1) contains a semicolon.
"
- "While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.
"
- "Unfortunately MO depends on libraries that seem to fall into that group.
"
- "As a result the python plugin can't be loaded.
"
- "The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon.").arg(QCoreApplication::applicationDirPath());
- } break;
- default:
- throw Exception(QString("invalid problem key %1").arg(key));
+ switch (static_cast(key)) {
+ case FailureType::SEMICOLON:
+ return tr("The path to Mod Organizer (%1) contains a semicolon.
"
+ "While this is legal on NTFS drives, many softwares do not handle it correctly.
"
+ "Unfortunately MO depends on libraries that seem to fall into that group.
"
+ "As a result the python plugin cannot be loaded, and the only solution we can"
+ "offer is to remove the semicolon or move MO to a path without a semicolon.").arg(QCoreApplication::applicationDirPath());
+ case FailureType::DLL_NOT_FOUND:
+ return tr("The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem.");
+ case FailureType::INVALID_DLL:
+ return tr("The Python plugin DLL is invalid, maybe your antivirus is blocking it. "
+ "Re-installing MO2 and adding exclusions for it to your AV might fix the problem.");
+ case FailureType::INITIALIZATION:
+ return tr("The initialization of the Python plugin DLL failed, unfortunately without any details.");
+ default:
+ return tr("invalid problem key %1").arg(key);
}
}
bool ProxyPython::hasGuidedFix(unsigned int key) const
{
- return (key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION);
+ return false;
}
void ProxyPython::startGuidedFix(unsigned int key) const
{
- if ((key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION)) {
- ::ShellExecuteA(nullptr, "open", s_DownloadPythonURL, nullptr, nullptr, SW_SHOWNORMAL);
- }
}
diff --git a/src/proxy/proxypython.h b/src/proxy/proxypython.h
index 4e17a3e..249d198 100644
--- a/src/proxy/proxypython.h
+++ b/src/proxy/proxypython.h
@@ -20,11 +20,14 @@ along with python proxy plugin. If not, see .
#ifndef PROXYPYTHON_H
#define PROXYPYTHON_H
+#include