2013-11-18 20:17:14 +01:00
|
|
|
/*
|
|
|
|
|
Copyright (C) 2013 Sebastian Herbord. All rights reserved.
|
|
|
|
|
|
|
|
|
|
This file is part of python proxy plugin for MO
|
|
|
|
|
|
|
|
|
|
python proxy plugin 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.
|
|
|
|
|
|
|
|
|
|
Python proxy plugin 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 python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
2013-05-10 10:57:35 +02:00
|
|
|
#include "proxypython.h"
|
2022-04-28 21:38:27 +02:00
|
|
|
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QDirIterator>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <QWidget>
|
|
|
|
|
#include <QtPlugin>
|
2022-04-28 21:38:27 +02:00
|
|
|
|
|
|
|
|
#include "log.h"
|
2013-05-10 10:57:35 +02:00
|
|
|
#include <utility.h>
|
|
|
|
|
#include <versioninfo.h>
|
|
|
|
|
|
2022-04-28 21:38:27 +02:00
|
|
|
namespace fs = std::filesystem;
|
2013-04-14 20:49:18 +02:00
|
|
|
using namespace MOBase;
|
|
|
|
|
|
2022-04-28 21:38:27 +02:00
|
|
|
// retrieve the path to the folder containing the proxy DLL
|
|
|
|
|
fs::path getPluginFolder()
|
|
|
|
|
{
|
|
|
|
|
wchar_t path[MAX_PATH];
|
|
|
|
|
HMODULE hm = NULL;
|
|
|
|
|
|
|
|
|
|
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
|
|
|
|
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
|
|
|
(LPCWSTR)&getPluginFolder, &hm) == 0) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
if (GetModuleFileName(hm, path, sizeof(path)) == 0) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fs::path(path).parent_path();
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-14 20:49:18 +02:00
|
|
|
ProxyPython::ProxyPython()
|
2022-04-25 21:21:06 +02:00
|
|
|
: m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr},
|
|
|
|
|
m_LoadFailure(FailureType::NONE)
|
2013-07-13 10:10:46 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
bool ProxyPython::init(IOrganizer* moInfo)
|
2013-04-14 20:49:18 +02:00
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
using CreatePythonRunner_func = IPythonRunner* (*)();
|
2022-02-13 17:33:53 +01:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
m_MOInfo = moInfo;
|
2020-11-04 22:52:56 -05:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
|
|
|
|
|
return false;
|
2022-02-13 17:33:53 +01:00
|
|
|
}
|
2022-04-25 21:21:06 +02:00
|
|
|
|
|
|
|
|
if (QCoreApplication::applicationDirPath().contains(';')) {
|
|
|
|
|
m_LoadFailure = FailureType::SEMICOLON;
|
|
|
|
|
return true;
|
2022-02-13 17:33:53 +01:00
|
|
|
}
|
2022-04-25 21:21:06 +02:00
|
|
|
|
2022-04-28 21:38:27 +02:00
|
|
|
const auto pluginFolder = getPluginFolder();
|
|
|
|
|
|
|
|
|
|
if (pluginFolder.empty()) {
|
|
|
|
|
DWORD error = ::GetLastError();
|
|
|
|
|
m_LoadFailure = FailureType::DLL_NOT_FOUND;
|
|
|
|
|
log::error("failed to resolve Python proxy directory ({}): {}", error,
|
|
|
|
|
qUtf8Printable(windowsErrorString(::GetLastError())));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
// load the pythonrunner library
|
2022-04-28 21:38:27 +02:00
|
|
|
const auto dllPaths = pluginFolder / "dlls";
|
|
|
|
|
if (SetDllDirectoryW(dllPaths.c_str()) == 0) {
|
|
|
|
|
DWORD error = ::GetLastError();
|
|
|
|
|
m_LoadFailure = FailureType::DLL_NOT_FOUND;
|
|
|
|
|
log::error("failed to add python DLL directory ({}): {}", error,
|
|
|
|
|
qUtf8Printable(windowsErrorString(::GetLastError())));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto runnerPath = pluginFolder / "pythonrunner.dll";
|
|
|
|
|
m_RunnerLib = ::LoadLibraryW(runnerPath.c_str());
|
2022-04-25 21:21:06 +02:00
|
|
|
|
|
|
|
|
if (!m_RunnerLib) {
|
|
|
|
|
DWORD error = ::GetLastError();
|
2022-04-28 21:38:27 +02:00
|
|
|
log::error("failed to load python runner ({}): {}", error,
|
2022-04-25 21:21:06 +02:00
|
|
|
qUtf8Printable(windowsErrorString(error)));
|
|
|
|
|
if (error == ERROR_MOD_NOT_FOUND) {
|
|
|
|
|
m_LoadFailure = FailureType::DLL_NOT_FOUND;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
m_LoadFailure = FailureType::INVALID_DLL;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()) {
|
2022-04-28 22:47:49 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2022-04-25 21:21:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_MOInfo) {
|
|
|
|
|
m_MOInfo->setPersistent(name(), "tryInit", true);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 22:44:15 +02:00
|
|
|
m_Runner = std::unique_ptr<IPythonRunner>{createPythonRunner()};
|
|
|
|
|
|
|
|
|
|
if (m_Runner) {
|
2022-05-02 18:56:44 +02:00
|
|
|
const auto libpath = pluginFolder / "libs";
|
|
|
|
|
const QStringList paths{
|
|
|
|
|
QFileInfo(libpath / "pythoncore.zip").absoluteFilePath(),
|
|
|
|
|
QFileInfo(libpath).absoluteFilePath(), IOrganizer::getPluginDataPath()};
|
|
|
|
|
m_Runner->initialize(paths);
|
2022-04-28 21:38:27 +02:00
|
|
|
}
|
2022-04-25 21:21:06 +02:00
|
|
|
|
|
|
|
|
if (m_MOInfo) {
|
|
|
|
|
m_MOInfo->setPersistent(name(), "tryInit", false);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 22:44:15 +02:00
|
|
|
if (!m_Runner || !m_Runner->isInitialized()) {
|
|
|
|
|
m_LoadFailure = FailureType::INITIALIZATION;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 21:38:27 +02:00
|
|
|
// reset DLL directory
|
|
|
|
|
SetDllDirectoryW(NULL);
|
2022-04-25 21:21:06 +02:00
|
|
|
|
2022-02-13 17:33:53 +01:00
|
|
|
return true;
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::name() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return "Python Proxy";
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-11 11:22:10 +01:00
|
|
|
QString ProxyPython::localizedName() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("Python Proxy");
|
2020-11-11 11:22:10 +01:00
|
|
|
}
|
|
|
|
|
|
2013-04-14 20:49:18 +02:00
|
|
|
QString ProxyPython::author() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return "AnyOldName3, Holt59, Silarn, Tannin";
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::description() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("Proxy Plugin to allow plugins written in python to be loaded");
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VersionInfo ProxyPython::version() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<PluginSetting> ProxyPython::settings() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return {};
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-01 21:16:43 +01:00
|
|
|
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
|
2013-04-14 20:49:18 +02:00
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
QDir dir(pluginPath);
|
|
|
|
|
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
|
|
|
|
|
QDirIterator iter(dir);
|
2013-04-14 20:49:18 +02:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
// Note: We put python script (.py) and directory names, not the __init__.py
|
|
|
|
|
// files in those since it is easier for the runner to import them.
|
|
|
|
|
QStringList result;
|
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
|
QString name = iter.next();
|
|
|
|
|
QFileInfo info = iter.fileInfo();
|
2020-05-11 23:51:58 +02:00
|
|
|
|
2022-04-26 13:36:52 +02:00
|
|
|
if (info.isFile() && name.endsWith(".py")) {
|
2022-04-25 21:21:06 +02:00
|
|
|
result.append(name);
|
|
|
|
|
}
|
|
|
|
|
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
|
2022-04-26 13:36:52 +02:00
|
|
|
result.append(name);
|
2022-04-25 21:21:06 +02:00
|
|
|
}
|
2020-05-11 23:51:58 +02:00
|
|
|
}
|
2022-04-21 23:57:05 +02:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
return result;
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-01 21:16:43 +01:00
|
|
|
QList<QObject*> ProxyPython::load(const QString& identifier)
|
2013-04-14 20:49:18 +02:00
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
if (!m_Runner) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2022-04-28 13:44:29 +02:00
|
|
|
return m_Runner->load(identifier);
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-01 21:16:43 +01:00
|
|
|
void ProxyPython::unload(const QString& identifier)
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
if (m_Runner) {
|
|
|
|
|
return m_Runner->unload(identifier);
|
|
|
|
|
}
|
2020-12-01 21:16:43 +01:00
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
|
|
|
|
|
std::vector<unsigned int> ProxyPython::activeProblems() const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
auto failure = m_LoadFailure;
|
2022-02-13 17:33:53 +01:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
// don't know how this could happen but wth
|
2022-04-28 21:38:27 +02:00
|
|
|
if (m_Runner && !m_Runner->isInitialized()) {
|
2022-04-25 21:21:06 +02:00
|
|
|
failure = FailureType::INITIALIZATION;
|
|
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
if (failure != FailureType::NONE) {
|
|
|
|
|
return {static_cast<std::underlying_type_t<FailureType>>(failure)};
|
|
|
|
|
}
|
2022-02-13 17:33:53 +01:00
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
return {};
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::shortDescription(unsigned int key) const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
switch (static_cast<FailureType>(key)) {
|
2022-02-13 17:33:53 +01:00
|
|
|
case FailureType::SEMICOLON:
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("ModOrganizer path contains a semicolon");
|
2022-02-13 17:33:53 +01:00
|
|
|
case FailureType::DLL_NOT_FOUND:
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("Python DLL not found");
|
2022-02-13 17:33:53 +01:00
|
|
|
case FailureType::INVALID_DLL:
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("Invalid Python DLL");
|
2022-02-13 17:33:53 +01:00
|
|
|
case FailureType::INITIALIZATION:
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("Initializing Python failed");
|
2013-07-13 10:10:46 +02:00
|
|
|
default:
|
2022-04-25 21:21:06 +02:00
|
|
|
return tr("invalid problem key %1").arg(key);
|
|
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::fullDescription(unsigned int key) const
|
|
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
switch (static_cast<FailureType>(key)) {
|
|
|
|
|
case FailureType::SEMICOLON:
|
|
|
|
|
return tr("The path to Mod Organizer (%1) contains a semicolon. <br>"
|
|
|
|
|
"While this is legal on NTFS drives, many softwares do not handle it "
|
|
|
|
|
"correctly.<br>"
|
|
|
|
|
"Unfortunately MO depends on libraries that seem to fall into that "
|
|
|
|
|
"group.<br>"
|
|
|
|
|
"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);
|
|
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-05 21:04:20 +02:00
|
|
|
bool ProxyPython::hasGuidedFix(unsigned int key) const
|
2013-07-13 10:10:46 +02:00
|
|
|
{
|
2022-04-25 21:21:06 +02:00
|
|
|
return false;
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-25 21:21:06 +02:00
|
|
|
void ProxyPython::startGuidedFix(unsigned int key) const {}
|