2013-04-14 20:49:18 +02:00
|
|
|
#pragma warning( push )
|
2013-05-10 10:57:35 +02:00
|
|
|
#pragma warning( disable : 4100 ) // a lot of unreferenced formal parameters from boost libraries
|
2013-08-17 08:49:36 +02:00
|
|
|
#pragma warning( disable : 4996 ) // strncpy claimed to be unsafe
|
2013-05-10 10:57:35 +02:00
|
|
|
|
|
|
|
|
#include "proxypython.h"
|
2013-04-14 20:49:18 +02:00
|
|
|
|
|
|
|
|
#pragma warning( pop )
|
|
|
|
|
|
2013-05-10 10:57:35 +02:00
|
|
|
#include <utility.h>
|
|
|
|
|
#include <versioninfo.h>
|
|
|
|
|
#include <QtPlugin>
|
|
|
|
|
#include <QDirIterator>
|
|
|
|
|
#include <QWidget>
|
2013-09-21 19:25:33 +02:00
|
|
|
#include <QMessageBox>
|
2013-09-28 21:13:57 +02:00
|
|
|
#include <QCoreApplication>
|
2013-09-01 18:36:43 +02:00
|
|
|
#include "resource.h"
|
2013-05-10 10:57:35 +02:00
|
|
|
|
2013-08-30 20:59:12 +02:00
|
|
|
|
2013-04-14 20:49:18 +02:00
|
|
|
using namespace MOBase;
|
|
|
|
|
|
2013-05-10 10:57:35 +02:00
|
|
|
|
2013-07-13 10:10:46 +02:00
|
|
|
const char *ProxyPython::s_DownloadPythonURL = "http://www.python.org/download/releases/";
|
|
|
|
|
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
HMODULE GetOwnModuleHandle()
|
2013-05-10 10:57:35 +02:00
|
|
|
{
|
2013-09-01 18:36:43 +02:00
|
|
|
HMODULE hMod = NULL;
|
|
|
|
|
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
|
|
|
reinterpret_cast<LPCWSTR>(&GetOwnModuleHandle), &hMod);
|
2013-04-14 20:49:18 +02:00
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
return hMod;
|
2013-05-10 10:57:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
QString ExtractResource(WORD resourceID, const QString &szFilename)
|
2013-05-10 10:57:35 +02:00
|
|
|
{
|
2013-09-01 18:36:43 +02:00
|
|
|
bool success = false;
|
|
|
|
|
|
|
|
|
|
HMODULE mod = GetOwnModuleHandle();
|
|
|
|
|
|
|
|
|
|
HRSRC hResource = FindResourceW(mod, MAKEINTRESOURCE(resourceID), L"BINARY");
|
|
|
|
|
if (hResource == NULL) {
|
|
|
|
|
throw MyException("embedded dll not available: " + windowsErrorString(::GetLastError()));
|
2013-05-10 10:57:35 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
HGLOBAL hFileResource = LoadResource(mod, hResource);
|
|
|
|
|
if (hFileResource == NULL) {
|
|
|
|
|
throw MyException("failed to load embedded dll resource: " + windowsErrorString(::GetLastError()));
|
2013-05-10 10:57:35 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
LPVOID lpFile = LockResource(hFileResource);
|
|
|
|
|
DWORD dwSize = SizeofResource(mod, hResource);
|
|
|
|
|
|
|
|
|
|
QString outFile = QDir::tempPath() + "/" + szFilename;
|
|
|
|
|
|
|
|
|
|
HANDLE hFile = CreateFileW(ToWString(outFile).c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
|
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, dwSize, NULL);
|
|
|
|
|
LPVOID lpAddress = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
CopyMemory(lpAddress, lpFile, dwSize);
|
|
|
|
|
|
|
|
|
|
UnmapViewOfFile(lpAddress);
|
|
|
|
|
|
|
|
|
|
CloseHandle(hFileMap);
|
|
|
|
|
CloseHandle(hFile);
|
|
|
|
|
|
|
|
|
|
return outFile;
|
2013-05-10 10:57:35 +02:00
|
|
|
}
|
|
|
|
|
|
2013-06-05 09:51:48 +02:00
|
|
|
|
2013-04-14 20:49:18 +02:00
|
|
|
ProxyPython::ProxyPython()
|
2013-09-01 18:36:43 +02:00
|
|
|
: m_MOInfo(NULL), m_Runner(NULL), m_LoadFailure(FAIL_NOTINIT)
|
2013-07-13 10:10:46 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
ProxyPython::~ProxyPython()
|
2013-04-14 20:49:18 +02:00
|
|
|
{
|
2013-09-01 18:36:43 +02:00
|
|
|
if (!m_TempRunnerFile.isEmpty()) {
|
|
|
|
|
::FreeLibrary(m_RunnerLib);
|
|
|
|
|
QFile(m_TempRunnerFile).remove();
|
2013-05-10 10:57:35 +02:00
|
|
|
}
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
typedef IPythonRunner* (*CreatePythonRunner_func)(const MOBase::IOrganizer *moInfo, const QString &pythonPath);
|
|
|
|
|
|
|
|
|
|
|
2013-04-14 20:49:18 +02:00
|
|
|
bool ProxyPython::init(IOrganizer *moInfo)
|
|
|
|
|
{
|
|
|
|
|
m_MOInfo = moInfo;
|
2013-09-21 19:25:33 +02:00
|
|
|
if (!m_MOInfo->pluginSetting(name(), "enabled").toBool()) {
|
|
|
|
|
m_LoadFailure = FAIL_NONE;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-09-01 18:36:43 +02:00
|
|
|
|
|
|
|
|
m_LoadFailure = FAIL_OTHER;
|
2013-09-28 21:13:57 +02:00
|
|
|
if (QCoreApplication::applicationDirPath().contains(';')) {
|
|
|
|
|
m_LoadFailure = FAIL_SEMICOLON;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2013-09-01 18:36:43 +02:00
|
|
|
|
2013-09-05 21:04:20 +02:00
|
|
|
QString pythonPath = m_MOInfo->pluginSetting(name(), "python_dir").toString();
|
|
|
|
|
|
|
|
|
|
if (!pythonPath.isEmpty() && !QFile::exists(pythonPath + "/python.exe")) {
|
|
|
|
|
m_LoadFailure = FAIL_WRONGPYTHONPATH;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-01 18:36:43 +02:00
|
|
|
m_TempRunnerFile = ExtractResource(IDR_LOADER_DLL, "__pythonRunner.dll");
|
|
|
|
|
m_RunnerLib = ::LoadLibraryW(ToWString(m_TempRunnerFile).c_str());
|
|
|
|
|
if (m_RunnerLib != NULL) {
|
|
|
|
|
CreatePythonRunner_func CreatePythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
|
|
|
|
|
if (CreatePythonRunner == NULL) {
|
|
|
|
|
throw MyException("embedded dll is invalid: " + windowsErrorString(::GetLastError()));
|
|
|
|
|
}
|
2013-09-21 19:25:33 +02:00
|
|
|
if (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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_MOInfo->setPersistent(name(), "tryInit", true);
|
2013-09-05 21:04:20 +02:00
|
|
|
m_Runner = CreatePythonRunner(moInfo, pythonPath);
|
2013-09-21 19:25:33 +02:00
|
|
|
m_MOInfo->setPersistent(name(), "tryInit", false);
|
|
|
|
|
|
2013-09-05 21:04:20 +02:00
|
|
|
if (m_Runner != NULL) {
|
|
|
|
|
m_LoadFailure = FAIL_NONE;
|
|
|
|
|
} else {
|
|
|
|
|
m_LoadFailure = FAIL_INITFAIL;
|
|
|
|
|
}
|
2013-09-01 18:36:43 +02:00
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
DWORD error = ::GetLastError();
|
|
|
|
|
qCritical("Failed to load python runner: %s", qPrintable(windowsErrorString(error)));
|
|
|
|
|
if (error == ERROR_MOD_NOT_FOUND) {
|
|
|
|
|
m_LoadFailure = FAIL_MISSINGDEPENDENCIES;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::name() const
|
|
|
|
|
{
|
|
|
|
|
return tr("Python Proxy");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::author() const
|
|
|
|
|
{
|
|
|
|
|
return "Tannin";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::description() const
|
|
|
|
|
{
|
|
|
|
|
return tr("Proxy Plugin to allow plugins written in python to be loaded");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VersionInfo ProxyPython::version() const
|
|
|
|
|
{
|
2013-09-21 19:25:33 +02:00
|
|
|
return VersionInfo(1, 2, 0, VersionInfo::RELEASE_FINAL);
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProxyPython::isActive() const
|
|
|
|
|
{
|
2013-09-01 18:36:43 +02:00
|
|
|
return m_LoadFailure == FAIL_NONE;
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<PluginSetting> ProxyPython::settings() const
|
|
|
|
|
{
|
2013-07-13 10:10:46 +02:00
|
|
|
QList<PluginSetting> result;
|
2013-09-01 18:36:43 +02:00
|
|
|
result.push_back(PluginSetting("python_dir", "Path to your python installation. Leave empty for auto-detection", ""));
|
2013-09-21 19:25:33 +02:00
|
|
|
result.push_back(PluginSetting("enabled", "Set to true to enable support for python plugins", true));
|
2013-07-13 10:10:46 +02:00
|
|
|
return result;
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList ProxyPython::pluginList(const QString &pluginPath) const
|
|
|
|
|
{
|
|
|
|
|
QDirIterator iter(pluginPath, QStringList("*.py"));
|
|
|
|
|
|
|
|
|
|
QStringList result;
|
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
|
result.append(iter.next());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-10 10:57:35 +02:00
|
|
|
QObject *ProxyPython::instantiate(const QString &pluginName)
|
2013-04-14 20:49:18 +02:00
|
|
|
{
|
2013-09-01 18:36:43 +02:00
|
|
|
if (m_Runner != NULL) {
|
2013-09-05 21:04:20 +02:00
|
|
|
QObject *result = m_Runner->instantiate(pluginName);
|
|
|
|
|
return result;
|
2013-09-01 18:36:43 +02:00
|
|
|
} else {
|
|
|
|
|
return NULL;
|
2013-04-14 20:49:18 +02:00
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<unsigned int> ProxyPython::activeProblems() const
|
|
|
|
|
{
|
|
|
|
|
std::vector<unsigned int> result;
|
2013-09-01 18:36:43 +02:00
|
|
|
if (m_LoadFailure == FAIL_MISSINGDEPENDENCIES) {
|
2013-07-13 10:10:46 +02:00
|
|
|
result.push_back(PROBLEM_PYTHONMISSING);
|
2013-09-05 21:04:20 +02:00
|
|
|
} else if (m_LoadFailure == FAIL_WRONGPYTHONPATH) {
|
|
|
|
|
result.push_back(PROBLEM_WRONGPYTHONPATH);
|
2013-09-21 19:25:33 +02:00
|
|
|
} else if (m_LoadFailure == FAIL_PYTHONDETECTION) {
|
|
|
|
|
result.push_back(PROBLEM_PYTHONDETECTION);
|
2013-09-05 21:04:20 +02:00
|
|
|
} else if (m_LoadFailure == FAIL_INITFAIL) {
|
|
|
|
|
result.push_back(PROBLEM_INITFAIL);
|
2013-09-28 21:13:57 +02:00
|
|
|
} else if (m_LoadFailure == FAIL_SEMICOLON) {
|
|
|
|
|
result.push_back(PROBLEM_SEMICOLON);
|
2013-09-01 18:36:43 +02:00
|
|
|
} else if (m_Runner != NULL) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::shortDescription(unsigned int key) const
|
|
|
|
|
{
|
|
|
|
|
switch (key) {
|
|
|
|
|
case PROBLEM_PYTHONMISSING: {
|
2013-09-05 21:04:20 +02:00
|
|
|
return tr("Python not installed or not found");
|
|
|
|
|
} break;
|
2013-07-13 10:10:46 +02:00
|
|
|
case PROBLEM_PYTHONWRONGVERSION: {
|
2013-09-05 21:04:20 +02:00
|
|
|
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;
|
2013-09-21 19:25:33 +02:00
|
|
|
case PROBLEM_PYTHONDETECTION: {
|
|
|
|
|
return tr("Python auto-detection failed");
|
|
|
|
|
} break;
|
2013-09-28 21:13:57 +02:00
|
|
|
case PROBLEM_SEMICOLON: {
|
|
|
|
|
return tr("ModOrganizer path contains a semicolon");
|
|
|
|
|
} break;
|
2013-07-13 10:10:46 +02:00
|
|
|
default:
|
|
|
|
|
throw MyException(tr("invalid problem key %1").arg(key));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QString ProxyPython::fullDescription(unsigned int key) const
|
|
|
|
|
{
|
|
|
|
|
switch (key) {
|
|
|
|
|
case PROBLEM_PYTHONMISSING: {
|
2013-09-16 22:32:42 +02:00
|
|
|
return tr("Some MO plugins require the python interpreter to be installed. "
|
2013-09-05 21:04:20 +02:00
|
|
|
"These plugins will not even show up in settings->plugins.<br>"
|
2013-09-16 22:32:42 +02:00
|
|
|
"If you want to use those plugins, please install the 32-bit version of Python 2.7.x from <a href=\"%1\">%1</a>.<br>"
|
|
|
|
|
"This is only required to use some extended functionality in MO, you do not need Python to play the game.").arg(s_DownloadPythonURL);
|
2013-09-05 21:04:20 +02:00
|
|
|
} break;
|
2013-07-13 10:10:46 +02:00
|
|
|
case PROBLEM_PYTHONWRONGVERSION: {
|
2013-09-05 21:04:20 +02:00
|
|
|
return tr("Your installed python version has a different version than 2.7. "
|
2013-09-16 22:32:42 +02:00
|
|
|
"Some MO plugins may not work.<br>"
|
2013-09-21 19:25:33 +02:00
|
|
|
"If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) "
|
2013-09-16 22:32:42 +02:00
|
|
|
"in the settings dialog.<br>"
|
|
|
|
|
"This is only required to use some extended functionality in MO, you do not need Python to play the game.");
|
2013-09-05 21:04:20 +02:00
|
|
|
} break;
|
|
|
|
|
case PROBLEM_WRONGPYTHONPATH: {
|
2013-09-21 19:25:33 +02:00
|
|
|
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.");
|
2013-09-05 21:04:20 +02:00
|
|
|
} break;
|
|
|
|
|
case PROBLEM_INITFAIL: {
|
|
|
|
|
return tr("Sorry, I don't know any details. Most likely your python installation is not supported.");
|
|
|
|
|
} break;
|
2013-09-28 21:13:57 +02:00
|
|
|
case PROBLEM_SEMICOLON: {
|
|
|
|
|
return tr("The path to Mod Organizer (%1) contains a semicolon. <br>"
|
|
|
|
|
"While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.<br>"
|
|
|
|
|
"Unfortunately MO depends on libraries that seem to fall into that group.<br>"
|
|
|
|
|
"As a result the python plugin can't be loaded.<br>"
|
|
|
|
|
"The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon.").arg(QCoreApplication::applicationDirPath());
|
|
|
|
|
} break;
|
2013-07-13 10:10:46 +02:00
|
|
|
default:
|
|
|
|
|
throw MyException(tr("invalid problem key %1").arg(key));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-05 21:04:20 +02:00
|
|
|
bool ProxyPython::hasGuidedFix(unsigned int key) const
|
2013-07-13 10:10:46 +02:00
|
|
|
{
|
2013-09-05 21:04:20 +02:00
|
|
|
return (key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION);
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-21 19:25:33 +02:00
|
|
|
void ProxyPython::startGuidedFix(unsigned int key) const
|
2013-07-13 10:10:46 +02:00
|
|
|
{
|
2013-09-21 19:25:33 +02:00
|
|
|
if ((key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION)) {
|
|
|
|
|
::ShellExecuteA(NULL, "open", s_DownloadPythonURL, NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
|
}
|
2013-07-13 10:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-15 14:42:20 +02:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
|
2013-04-14 20:49:18 +02:00
|
|
|
Q_EXPORT_PLUGIN2(proxyPython, ProxyPython)
|
2013-06-15 14:42:20 +02:00
|
|
|
#endif
|