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 +#include + +#include #include #include -#include -#include + #include @@ -38,59 +41,42 @@ class ProxyPython : public QObject, public MOBase::IPluginProxy, public MOBase:: public: ProxyPython(); - ~ProxyPython(); virtual bool init(MOBase::IOrganizer *moInfo); - virtual QString name() const; - virtual QString localizedName() const; - virtual QString author() const; - virtual QString description() const; - virtual MOBase::VersionInfo version() const; - virtual QList settings() const; + virtual QString name() const override; + virtual QString localizedName() const override; + virtual QString author() const override; + virtual QString description() const override; + virtual MOBase::VersionInfo version() const override; + virtual QList settings() const override; - QStringList pluginList(const QDir& pluginPath) const; - QList load(const QString& identifier); - void unload(const QString& identifier); - - /** - * @return the parent widget for newly created dialogs - * @note needs to be public so it can be exposed to plugins - */ - virtual QWidget *getParentWidget() { return parentWidget(); } + QStringList pluginList(const QDir& pluginPath) const override; + QList load(const QString& identifier) override; + void unload(const QString& identifier) override; public: // IPluginDiagnose - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; + virtual std::vector activeProblems() const override; + virtual QString shortDescription(unsigned int key) const override; + virtual QString fullDescription(unsigned int key) const override; + virtual bool hasGuidedFix(unsigned int key) const override; + virtual void startGuidedFix(unsigned int key) const override; private: - static const unsigned int PROBLEM_PYTHONMISSING = 1; - static const unsigned int PROBLEM_PYTHONWRONGVERSION = 2; - static const unsigned int PROBLEM_WRONGPYTHONPATH = 3; - static const unsigned int PROBLEM_INITFAIL = 4; - static const unsigned int PROBLEM_PYTHONDETECTION = 5; - static const unsigned int PROBLEM_SEMICOLON = 6; - static const char *s_DownloadPythonURL; - MOBase::IOrganizer *m_MOInfo; - QString m_TempRunnerFile; HMODULE m_RunnerLib; - IPythonRunner *m_Runner; + std::unique_ptr m_Runner; - enum { - FAIL_NONE, - FAIL_SEMICOLON, - FAIL_NOTINIT, - FAIL_MISSINGDEPENDENCIES, - FAIL_INITFAIL, - FAIL_WRONGPYTHONPATH, - FAIL_PYTHONDETECTION, - FAIL_OTHER - } m_LoadFailure; + enum class FailureType : unsigned int { + NONE = 0, + SEMICOLON = 1, + DLL_NOT_FOUND = 2, + INVALID_DLL = 3, + INITIALIZATION = 4 + }; + + FailureType m_LoadFailure; }; diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 79b896c..d55acec 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -1164,12 +1164,12 @@ public: PythonRunner(); ~PythonRunner(); - bool initPython(const QString& pythonDir); + bool initPython(); QList load(const QString& identifier); void unload(const QString& identifier); - bool isPythonInstalled() const; + bool isPythonInitialized() const; bool isPythonVersionSupported() const; private: @@ -1201,14 +1201,13 @@ private: wchar_t* m_PythonHome; }; -IPythonRunner* CreatePythonRunner(const QString& pythonDir) +IPythonRunner* CreatePythonRunner() { - PythonRunner* result = new PythonRunner; - if (result->initPython(pythonDir)) { - return result; + std::unique_ptr result = std::make_unique(); + if (result->initPython()) { + return result.release(); } else { - delete result; return nullptr; } } @@ -1306,19 +1305,11 @@ BOOST_PYTHON_MODULE(moprivate) }, bpy::arg("callback") = bpy::object{}); } -bool PythonRunner::initPython(const QString &pythonPath) +bool PythonRunner::initPython() { if (Py_IsInitialized()) return true; try { - if (!pythonPath.isEmpty() && !QFile::exists(pythonPath + "/python.exe")) { - return false; - } - pythonPath.toWCharArray(m_PythonHome); - if (!pythonPath.isEmpty()) { - Py_SetPythonHome(m_PythonHome); - } - wchar_t argBuffer[MAX_PATH]; const size_t cSize = strlen(argv0) + 1; mbstowcs(argBuffer, argv0, MAX_PATH); @@ -1568,15 +1559,7 @@ void PythonRunner::unload(const QString& identifier) } } -bool PythonRunner::isPythonInstalled() const +bool PythonRunner::isPythonInitialized() const { return Py_IsInitialized() != 0; } - - -bool PythonRunner::isPythonVersionSupported() const -{ - const char *version = Py_GetVersion(); - return strstr(version, "3.8") == version; -} - diff --git a/src/runner/pythonrunner.h b/src/runner/pythonrunner.h index be47860..ce8cad6 100644 --- a/src/runner/pythonrunner.h +++ b/src/runner/pythonrunner.h @@ -14,8 +14,7 @@ public: virtual QList load(const QString& identifier) = 0; virtual void unload(const QString& identifier) = 0; - virtual bool isPythonInstalled() const = 0; - virtual bool isPythonVersionSupported() const = 0; + virtual bool isPythonInitialized() const = 0; virtual ~IPythonRunner() { } }; @@ -27,7 +26,7 @@ public: #define PYDLLEXPORT Q_DECL_IMPORT #endif // PYTHONRUNNER_LIBRARY -extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner(const QString &pythonDir); +extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner();