Compare commits

..
1 Commits
Author SHA1 Message Date
isanae c922b97578 Merge pull request #56 from Holt59/fix-ifiletree-copy
Fix IFileTree copy
2020-08-06 09:00:16 -04:00
24 changed files with 564 additions and 1275 deletions
+4 -12
View File
@@ -7,22 +7,14 @@ environment:
build:
parallel: true
build_script:
- pwsh: >-
$ErrorActionPreference = 'Stop'
- cmd: >-
git clone --depth=1 --branch=%APPVEYOR_REPO_BRANCH% https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella 2> $null
git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella
New-Item -ItemType Directory -Path c:\projects\modorganizer-build
mkdir c:\projects\modorganizer-build -type directory
cd c:\projects\modorganizer-umbrella
($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH)
git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch}
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME}
if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True %APPVEYOR_PROJECT_NAME%
artifacts:
- path: build\src\proxy\plugin_python.dll
name: plugin_python_dll
+218 -108
View File
@@ -26,86 +26,156 @@ along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
#include <QWidget>
#include <QMessageBox>
#include <QCoreApplication>
#include "log.h"
#include "resource.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<LPCWSTR>(&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 MyException("embedded dll not available: " + windowsErrorString(::GetLastError()));
}
HGLOBAL hFileResource = LoadResource(mod, hResource);
if (hFileResource == nullptr) {
throw MyException("failed to load embedded dll resource: " + windowsErrorString(::GetLastError()));
}
LPVOID lpFile = LockResource(hFileResource);
if (lpFile == nullptr) {
throw MyException(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 MyException(QString("failed to open python runner: %1").arg(windowsErrorString(::GetLastError())));
}
}
HANDLE hFileMap = CreateFileMapping(hFile, nullptr, PAGE_READWRITE, 0, dwSize, nullptr);
if (hFileMap == NULL) {
throw MyException(QString("failed to map python runner: %1").arg(windowsErrorString(::GetLastError())));
}
LPVOID lpAddress = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);
if (lpAddress == nullptr) {
throw MyException(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_RunnerLib{ nullptr },
m_Runner{ nullptr },
m_LoadFailure(FailureType::NONE)
: m_MOInfo(nullptr), m_Runner(nullptr), m_LoadFailure(FAIL_NOTINIT)
{
}
ProxyPython::~ProxyPython()
{
if (!m_TempRunnerFile.isEmpty()) {
::FreeLibrary(m_RunnerLib);
QFile(m_TempRunnerFile).remove();
}
}
typedef IPythonRunner* (*CreatePythonRunner_func)(const MOBase::IOrganizer *moInfo, const QString &pythonPath);
bool ProxyPython::init(IOrganizer *moInfo)
{
using CreatePythonRunner_func = IPythonRunner * (*)();
m_MOInfo = moInfo;
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
if (!m_MOInfo->pluginSetting(name(), "enabled").toBool()) {
m_LoadFailure = FAIL_NONE;
return false;
}
m_LoadFailure = FAIL_OTHER;
if (QCoreApplication::applicationDirPath().contains(';')) {
m_LoadFailure = FailureType::SEMICOLON;
m_LoadFailure = FAIL_SEMICOLON;
return true;
}
// load the pythonrunner library
m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(
IOrganizer::getPluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
QString 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;
}
if (!pythonPath.isEmpty() && !QFile::exists(pythonPath + "/python.exe")) {
m_LoadFailure = FAIL_WRONGPYTHONPATH;
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()) {
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(m_MOInfo->pluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
if (m_RunnerLib != nullptr) {
CreatePythonRunner_func CreatePythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
if (CreatePythonRunner == nullptr) {
throw MyException("embedded dll is invalid: " + windowsErrorString(::GetLastError()));
}
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;
}
}
}
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", true);
}
m_Runner = std::unique_ptr<IPythonRunner>{ createPythonRunner() };
if (m_MOInfo) {
m_Runner = CreatePythonRunner(moInfo, pythonPath);
m_MOInfo->setPersistent(name(), "tryInit", false);
}
if (!m_Runner) {
m_LoadFailure = FailureType::INITIALIZATION;
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;
}
return true;
}
QString ProxyPython::name() const
@@ -113,14 +183,9 @@ QString ProxyPython::name() const
return "Python Proxy";
}
QString ProxyPython::localizedName() const
{
return tr("Python Proxy");
}
QString ProxyPython::author() const
{
return "AnyOldName3, Holt59, Silarn, Tannin";
return "Tannin";
}
QString ProxyPython::description() const
@@ -130,15 +195,23 @@ QString ProxyPython::description() const
VersionInfo ProxyPython::version() const
{
return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
return VersionInfo(2, 1, 0, VersionInfo::RELEASE_FINAL);
}
bool ProxyPython::isActive() const
{
return m_LoadFailure == FAIL_NOTINIT;
}
QList<PluginSetting> ProxyPython::settings() const
{
return {};
QList<PluginSetting> 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;
}
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
QStringList ProxyPython::pluginList(const QString &pluginPath) const
{
QDir dir(pluginPath);
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
@@ -151,92 +224,129 @@ QStringList ProxyPython::pluginList(const QDir& pluginPath) const
QString name = iter.next();
QFileInfo info = iter.fileInfo();
if (info.isFile() && name.endsWith(".py")) {
result.append(name);
}
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
result.append(name);
// result.append(info.baseName());
}
}
return result;
}
QList<QObject*> ProxyPython::load(const QString& identifier)
QList<QObject*> ProxyPython::instantiate(const QString &pluginName)
{
if (!m_Runner) {
return {};
if (m_Runner != nullptr) {
QList<QObject*> result = m_Runner->instantiate(pluginName);
return result;
} else {
return QList<QObject*>();
}
return m_Runner->load(identifier);
}
void ProxyPython::unload(const QString& identifier)
{
if (m_Runner) {
return m_Runner->unload(identifier);
}
}
std::vector<unsigned int> ProxyPython::activeProblems() const
{
auto failure = m_LoadFailure;
// don't know how this could happen but wth
if (m_Runner && !m_Runner->isPythonInitialized()) {
failure = FailureType::INITIALIZATION;
std::vector<unsigned int> 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);
}
}
if (failure != FailureType::NONE) {
return { static_cast<std::underlying_type_t<FailureType>>(failure) };
}
return {};
return result;
}
QString ProxyPython::shortDescription(unsigned int key) const
{
switch (static_cast<FailureType>(key)) {
case FailureType::SEMICOLON:
return tr("ModOrganizer path contains a semicolon");
case FailureType::DLL_NOT_FOUND:
return tr("Python DLL not found");
case FailureType::INVALID_DLL:
return tr("Invalid Python DLL");
case FailureType::INITIALIZATION:
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: {
return tr("ModOrganizer path contains a semicolon");
} break;
default:
return tr("invalid problem key %1").arg(key);
throw MyException(tr("invalid problem key %1").arg(key));
}
}
QString ProxyPython::fullDescription(unsigned int key) const
{
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);
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-&gt;plugins.<br>"
"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);
} break;
case PROBLEM_PYTHONWRONGVERSION: {
return tr("Your installed python version has a different version than 2.7. "
"Some MO plugins may not work.<br>"
"If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) "
"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.");
} 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. <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;
default:
throw MyException(QString("invalid problem key %1").arg(key));
}
}
bool ProxyPython::hasGuidedFix(unsigned int key) const
{
return false;
return (key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION);
}
void ProxyPython::startGuidedFix(unsigned int key) const
{
if ((key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION)) {
::ShellExecuteA(nullptr, "open", s_DownloadPythonURL, nullptr, nullptr, SW_SHOWNORMAL);
}
}
+42 -29
View File
@@ -20,14 +20,11 @@ along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
#ifndef PROXYPYTHON_H
#define PROXYPYTHON_H
#include <map>
#include <memory>
#include <Windows.h>
#include <ipluginproxy.h>
#include <iplugindiagnose.h>
#include <map>
#include <Windows.h>
#include <pythonrunner.h>
@@ -41,42 +38,58 @@ class ProxyPython : public QObject, public MOBase::IPluginProxy, public MOBase::
public:
ProxyPython();
~ProxyPython();
virtual bool init(MOBase::IOrganizer *moInfo);
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<MOBase::PluginSetting> settings() const override;
virtual QString name() const;
virtual QString author() const;
virtual QString description() const;
virtual MOBase::VersionInfo version() const;
virtual bool isActive() const;
virtual QList<MOBase::PluginSetting> settings() const;
QStringList pluginList(const QDir& pluginPath) const override;
QList<QObject*> load(const QString& identifier) override;
void unload(const QString& identifier) override;
QStringList pluginList(const QString &pluginPath) const;
QList<QObject*> instantiate(const QString &pluginName);
/**
* @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(); }
public: // IPluginDiagnose
virtual std::vector<unsigned int> 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;
virtual std::vector<unsigned int> 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;
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;
std::unique_ptr<IPythonRunner> m_Runner;
IPythonRunner *m_Runner;
enum class FailureType : unsigned int {
NONE = 0,
SEMICOLON = 1,
DLL_NOT_FOUND = 2,
INVALID_DLL = 3,
INITIALIZATION = 4
};
FailureType m_LoadFailure;
enum {
FAIL_NONE,
FAIL_SEMICOLON,
FAIL_NOTINIT,
FAIL_MISSINGDEPENDENCIES,
FAIL_INITFAIL,
FAIL_WRONGPYTHONPATH,
FAIL_PYTHONDETECTION,
FAIL_OTHER
} m_LoadFailure;
};
+86
View File
@@ -1,6 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>ProxyPython</name>
<message>
<location filename="proxy/proxypython.cpp" line="149"/>
<source>Python Initialization failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="150"/>
<source>On a previous start the Python Plugin failed to initialize.
Either the value in Settings-&gt;Plugins-&gt;ProxyPython-&gt;plugin_dir is set incorrectly or it is empty and auto-detection doesn&apos;t work for whatever reason.
Do you want to try initializing python again (at the risk of another crash)?
Suggestion: Select &quot;no&quot;, and click the warning sign for further help. Afterwards you have to re-enable the python plugin.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="193"/>
<source>Proxy Plugin to allow plugins written in python to be loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="281"/>
<source>Python not installed or not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="284"/>
<source>Python version is incompatible</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="287"/>
<source>Invalid python path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="290"/>
<source>Initializing Python failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="293"/>
<source>Python auto-detection failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="296"/>
<source>ModOrganizer path contains a semicolon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="299"/>
<source>invalid problem key %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="308"/>
<source>Some MO plugins require the python interpreter to be installed. These plugins will not even show up in settings-&amp;gt;plugins.&lt;br&gt;If you want to use those plugins, please install the 32-bit version of Python 2.7.x from &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;.&lt;br&gt;This is only required to use some extended functionality in MO, you do not need Python to play the game.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="314"/>
<source>Your installed python version has a different version than 2.7. Some MO plugins may not work.&lt;br&gt;If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) in the settings dialog.&lt;br&gt;This is only required to use some extended functionality in MO, you do not need Python to play the game.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="321"/>
<source>Please set python_dir in Settings-&gt;Plugins-&gt;ProxyPython to the path of your python 2.7 (32 bit) installation.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="324"/>
<source>The auto-detection of the python path failed. I don&apos;t know why this would happen but you can try to fix it by setting python_dir in Settings-&gt;Plugins-&gt;ProxyPython to the path of your python 2.7 (32 bit) installation.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="328"/>
<source>Sorry, I don&apos;t know any details. Most likely your python installation is not supported.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxy/proxypython.cpp" line="331"/>
<source>The path to Mod Organizer (%1) contains a semicolon. &lt;br&gt;While this is legal on NTFS drives there is a lot of software that doesn&apos;t handle it correctly.&lt;br&gt;Unfortunately MO depends on libraries that seem to fall into that group.&lt;br&gt;As a result the python plugin can&apos;t be loaded.&lt;br&gt;The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
-28
View File
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16)
project(pythonrunner)
set(project_type plugin)
set(enable_warnings OFF)
set(enable_bigobj ON)
set(install_dir bin/plugins/data)
set(create_translations ON)
@@ -23,30 +22,3 @@ endif()
requires_project(game_features)
requires_library(python)
add_filter(NAME src/converters GROUPS
converters
pythonutils
shared_ptr_converter
tuple_helper
variant_helper
)
add_filter(NAME src/runner GROUPS
pythonrunner
pylogger
widgets
)
add_filter(NAME src/utils GROUPS
error
gilock
sipapiaccess
)
add_filter(NAME src/wrappers GROUPS
gamefeatureswrappers
proxypluginwrappers
pythonwrapperutilities
uibasewrappers
)
+10 -55
View File
@@ -26,7 +26,7 @@ namespace utils {
struct QString_to_python_str
{
static PyObject* convert(const QString& str) {
// It's safer to explicitly convert to unicode as if we don't, this can return
// It's safer to explicitly convert to unicode as if we don't, this can return
// either str or unicode without it being easy to know which to expect
bpy::object pyStr = bpy::object(qUtf8Printable(str));
if (SIPBytes_Check(pyStr.ptr()))
@@ -66,37 +66,6 @@ namespace utils {
}
namespace Enum_converter {
/**
*
*/
template <typename Enum>
struct Enum_to_int
{
static PyObject* convert(const Enum& flags) {
return bpy::incref(bpy::object(static_cast<int>(flags)).ptr());
}
};
template <typename Enum>
struct Enum_from_python_obj
{
static void* convertible(PyObject* objPtr) {
return SIPLong_Check(objPtr) ? objPtr : nullptr;
}
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
int intVersion = (int)SIPLong_AsLong(objPtr);
void* storage = ((bpy::converter::rvalue_from_python_storage<Enum>*)data)->storage.bytes;
new (storage) Enum(static_cast<Enum>(intVersion));
data->convertible = storage;
}
};
}
namespace QFlags_converter {
/**
@@ -362,18 +331,14 @@ namespace utils {
};
template <class T, class... Ws>
struct wrap_impl<boost::reference_wrapper<T>, Ws... > : public wrap_impl<> {
using wrap_impl<>::apply;
struct wrap_impl<boost::reference_wrapper<T>, Ws... > {
static auto apply(T t) {
return boost::ref(t);
}
};
template <class T, class... Ws>
struct wrap_impl<boost::python::pointer_wrapper<T>, Ws... > : public wrap_impl<> {
using wrap_impl<>::apply;
struct wrap_impl<boost::python::pointer_wrapper<T>, Ws... > {
static auto apply(T t) {
return bpy::ptr(t);
}
@@ -399,7 +364,7 @@ namespace utils {
template <typename R, typename... Args, typename... Wrappers>
struct Functor_converter<R(Args...), Wrappers... >
{
template <class T>
static decltype(auto) wrap(T&& t) {
return details::wrap_impl<Wrappers... >::apply(std::forward<T>(t));
@@ -514,8 +479,8 @@ namespace utils {
using namespace QString_converter;
bpy::to_python_converter<QString, QString_to_python_str>();
bpy::converter::registry::push_back(
&QString_from_python_str::convertible,
&QString_from_python_str::construct,
&QString_from_python_str::convertible,
&QString_from_python_str::construct,
bpy::type_id<QString>());
}
@@ -523,8 +488,8 @@ namespace utils {
using namespace QVariant_converter;
bpy::to_python_converter<QVariant, QVariant_to_python_obj>();
bpy::converter::registry::push_back(
&QVariant_from_python_obj::convertible,
&QVariant_from_python_obj::construct,
&QVariant_from_python_obj::convertible,
&QVariant_from_python_obj::construct,
bpy::type_id<QVariant>());
}
@@ -539,16 +504,6 @@ namespace utils {
bpy::type_id<Flags>());
}
template <class Enum>
inline void register_enum_converter() {
using namespace Enum_converter;
bpy::to_python_converter<Enum, Enum_to_int<Enum>>();
bpy::converter::registry::push_back(
&Enum_from_python_obj<Enum>::convertible,
&Enum_from_python_obj<Enum>::construct,
bpy::type_id<Enum>());
}
template <class QClass>
inline void register_qclass_converter() {
using Converter = QClass_converter::QClass_converters<QClass>;
@@ -569,8 +524,8 @@ namespace utils {
inline void register_functor_converter() {
using Converter = Functor_converter<Fn, Wrappers... >;
bpy::converter::registry::push_back(
&Converter::convertible,
&Converter::construct,
&Converter::convertible,
&Converter::construct,
bpy::type_id<std::function<Fn>>());
}
+8 -8
View File
@@ -31,10 +31,10 @@ namespace pyexcept {
* @brief Exception to throw when a python implementation does not implement
* a pure virtual function.
*/
class MissingImplementation : public MOBase::Exception {
class MissingImplementation : public MOBase::MyException {
public:
MissingImplementation(std::string const& className, std::string const& methodName) :
Exception(QString::fromStdString(
MyException(QString::fromStdString(
fmt::format("Python class implementing \"{}\" has no implementation of method \"{}\".",
className, methodName))) { }
@@ -43,21 +43,21 @@ namespace pyexcept {
/**
* @brief Exception to throw when a python error occurs.
*/
class PythonError : public MOBase::Exception {
class PythonError : public MOBase::MyException {
public:
/**
* @brief Create a new PythonError, fetching the error message from python. If the message
* cannot be retrieved, `defaultErrorMessage()` is used instead.
*/
PythonError() : Exception(getPythonErrorMessage()) { }
PythonError() : MyException(getPythonErrorMessage()) { }
/**
* @brief Create a new PythonError with the given message.
*
* @param message Message for the exception.
*/
PythonError(QString message) : Exception(message) { }
PythonError(QString message) : MyException(message) { }
protected:
@@ -91,7 +91,7 @@ namespace pyexcept {
* @brief Exception to throw when an unknown error occured. This is typically thrown
* from a catch(...) block.
*/
class UnknownException : public MOBase::Exception {
class UnknownException : public MOBase::MyException {
public:
/**
@@ -99,14 +99,14 @@ namespace pyexcept {
*
* @see defaultErrorMessage
*/
UnknownException() : Exception(defaultErrorMessage()) { }
UnknownException() : MyException(defaultErrorMessage()) { }
/**
* @brief Create a new UnknownException with the given message.
*
* @param message Message for the exception.
*/
UnknownException(QString message) : Exception(message) { }
UnknownException(QString message) : MyException(message) { }
protected:
+28 -17
View File
@@ -1,6 +1,7 @@
#include "gamefeatureswrappers.h"
#include <any>
#include <boost/any.hpp>
#include <typeindex>
#include <ipluginlist.h>
@@ -8,7 +9,6 @@
#include <isavegame.h>
#include <isavegameinfowidget.h>
#include "shared_ptr_converter.h"
#include "ifiletree.h"
#include "pythonwrapperutilities.h"
@@ -108,9 +108,7 @@ ModDataChecker::CheckReturn ModDataCheckerWrapper::dataLooksValid(std::shared_pt
}
std::shared_ptr<MOBase::IFileTree> ModDataCheckerWrapper::fix(std::shared_ptr<MOBase::IFileTree> fileTree) const {
return utils::clean_shared_ptr(
basicWrapperFunctionImplementationWithDefault<std::shared_ptr<MOBase::IFileTree>>(
this, [](auto&&... args) { return nullptr; }, "fix", fileTree));
return basicWrapperFunctionImplementationWithDefault<std::shared_ptr<MOBase::IFileTree>>(this, [](auto&&... args) { return nullptr; }, "fix", fileTree);
}
/// end ModDataChecker Wrapper
@@ -128,9 +126,15 @@ std::vector<int> ModDataContentWrapper::getContentsFor(std::shared_ptr<const MOB
/////////////////////////////
/// SaveGameInfo Wrapper
SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(MOBase::ISaveGame const& save) const
MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const
{
return basicWrapperFunctionImplementation<SaveGameInfoWrapper::MissingAssets>(this, "getMissingAssets", boost::ref(save));
return basicWrapperFunctionImplementation<MOBase::ISaveGame*>(this, m_SaveGames[file], "getSaveGameInfo", file);
}
SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const
{
return basicWrapperFunctionImplementation<SaveGameInfoWrapper::MissingAssets>(this, "getMissingAssets", file);
}
MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const
@@ -138,6 +142,10 @@ MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* par
return basicWrapperFunctionImplementation<MOBase::ISaveGameInfoWidget*>(this, m_SaveGameWidget, "getSaveGameWidget", parent);
}
bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const
{
return basicWrapperFunctionImplementation<bool>(this, "hasScriptExtenderSave", file);
}
/// end SaveGameInfo Wrapper
/////////////////////////////
/// ScriptExtender Wrapper
@@ -162,9 +170,9 @@ QString ScriptExtenderWrapper::loaderPath() const
return basicWrapperFunctionImplementation<QString>(this, "loaderPath");
}
QString ScriptExtenderWrapper::savegameExtension() const
QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const
{
return basicWrapperFunctionImplementation<QString>(this, "savegameExtension");
return basicWrapperFunctionImplementation<QStringList>(this, "saveGameAttachmentExtensions");
}
bool ScriptExtenderWrapper::isInstalled() const
@@ -212,7 +220,7 @@ QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const
game_features_map_from_python::game_features_map_from_python()
{
boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id<std::map<std::type_index, std::any>>());
boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id<std::map<std::type_index, boost::any>>());
}
void * game_features_map_from_python::convertible(PyObject * objPtr)
@@ -221,15 +229,15 @@ void * game_features_map_from_python::convertible(PyObject * objPtr)
}
template<typename T>
void insertGameFeature(std::map<std::type_index, std::any>& map, const boost::python::object& pyObject)
void insertGameFeature(std::map<std::type_index, boost::any>& map, const boost::python::object& pyObject)
{
map[std::type_index(typeid(T))] = boost::python::extract<T*>(pyObject)();
}
void game_features_map_from_python::construct(PyObject * objPtr, boost::python::converter::rvalue_from_python_stage1_data * data)
{
void *storage = ((boost::python::converter::rvalue_from_python_storage<std::map<std::type_index, std::any>>*)data)->storage.bytes;
std::map<std::type_index, std::any> *result = new (storage) std::map<std::type_index, std::any>();
void *storage = ((boost::python::converter::rvalue_from_python_storage<std::map<std::type_index, boost::any>>*)data)->storage.bytes;
std::map<std::type_index, boost::any> *result = new (storage) std::map<std::type_index, boost::any>();
boost::python::dict source(boost::python::handle<>(boost::python::borrowed(objPtr)));
boost::python::list keys = source.keys();
int len = boost::python::len(keys);
@@ -308,7 +316,7 @@ void registerGameFeaturesPythonConverters()
.def("getContentsFor", bpy::pure_virtual(&ModDataContent::getContentsFor), bpy::arg("filetree"))
;
bpy::class_<ModDataContent::Content>("Content",
bpy::class_<ModDataContent::Content>("Content",
bpy::init<int, QString, QString, bpy::optional<bool>>((bpy::arg("id"), "name", "icon", bpy::arg("filter_only") = false)))
.add_property("id", &ModDataContent::Content::id)
.add_property("name", &ModDataContent::Content::name)
@@ -319,9 +327,12 @@ void registerGameFeaturesPythonConverters()
}
bpy::class_<SaveGameInfoWrapper, boost::noncopyable>("SaveGameInfo")
.def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("save"))
.def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy<bpy::manage_new_object>(),
.def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy<bpy::manage_new_object>(),
bpy::arg("filepath"))
.def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("filepath"))
.def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy<bpy::manage_new_object>(),
bpy::arg("parent"), "[optional]")
.def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave), bpy::arg("filepath"))
;
bpy::class_<ScriptExtenderWrapper, boost::noncopyable>("ScriptExtender")
@@ -329,7 +340,7 @@ void registerGameFeaturesPythonConverters()
.def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath))
.def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName))
.def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath))
.def("savegameExtension", bpy::pure_virtual(&ScriptExtender::savegameExtension))
.def("saveGameAttachmentExtensions", bpy::pure_virtual(&ScriptExtender::saveGameAttachmentExtensions))
.def("isInstalled", bpy::pure_virtual(&ScriptExtender::isInstalled))
.def("getExtenderVersion", bpy::pure_virtual(&ScriptExtender::getExtenderVersion))
.def("getArch", bpy::pure_virtual(&ScriptExtender::getArch))
+4 -2
View File
@@ -106,8 +106,10 @@ public:
static constexpr const char* className = "SaveGameInfoWrapper";
using boost::python::wrapper<SaveGameInfo>::get_override;
virtual MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const override;
virtual MOBase::ISaveGame const *getSaveGameInfo(QString const &file) const override;
virtual MissingAssets getMissingAssets(QString const &file) const override;
virtual MOBase::ISaveGameInfoWidget *getSaveGameWidget(QWidget *parent = 0) const override;
virtual bool hasScriptExtenderSave(QString const &file) const override;
private:
// We need to keep the python objects alive:
@@ -125,7 +127,7 @@ public:
virtual QString PluginPath() const override;
virtual QString loaderName() const override;
virtual QString loaderPath() const override;
virtual QString savegameExtension() const override;
virtual QStringList saveGameAttachmentExtensions() const override;
virtual bool isInstalled() const override;
virtual QString getExtenderVersion() const override;
virtual WORD getArch() const override;
-1
View File
@@ -7,6 +7,5 @@ GILock::GILock()
GILock::~GILock()
{
PyErr_Clear();
PyGILState_Release(m_State);
}
+23 -54
View File
@@ -4,9 +4,7 @@
#include <QUrl>
#include <QWidget>
#include "shared_ptr_converter.h"
#include "pythonwrapperutilities.h"
#include "uibasewrappers.h"
#include <variant>
#include <tuple>
@@ -28,9 +26,8 @@ namespace boost
using namespace MOBase;
// See COMMON_I_PLUGIN_WRAPPER_DECLARATIONS__IMPL in proxypluginwrappers.h for explanation on
// the "include_requirements".
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, include_requirements) \
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) \
bool class_name::init(MOBase::IOrganizer *moInfo) \
{ \
return basicWrapperFunctionImplementation<bool>(this, "init", boost::python::ptr(moInfo)); \
@@ -41,16 +38,6 @@ QString class_name::name() const \
return basicWrapperFunctionImplementation<QString>(this, "name"); \
} \
\
QString class_name::localizedName() const \
{ \
return basicWrapperFunctionImplementationWithDefault<QString>(this, &class_name::localizedName_Default, "localizedName"); \
} \
\
QString class_name::master() const \
{ \
return basicWrapperFunctionImplementationWithDefault<QString>(this, &class_name::master_Default, "master"); \
} \
\
QString class_name::author() const \
{ \
return basicWrapperFunctionImplementation<QString>(this, "author"); \
@@ -66,25 +53,15 @@ MOBase::VersionInfo class_name::version() const \
return basicWrapperFunctionImplementation<MOBase::VersionInfo>(this, "version"); \
} \
\
bool class_name::isActive() const \
{ \
return basicWrapperFunctionImplementation<bool>(this, "isActive"); \
} \
\
QList<MOBase::PluginSetting> class_name::settings() const \
{ \
return basicWrapperFunctionImplementation<QList<MOBase::PluginSetting>>(this, "settings"); \
} \
QString class_name::localizedName_Default() const { return IPlugin::localizedName(); } \
QString class_name::master_Default() const { return IPlugin::master(); } \
BOOST_PP_EXPR_IF(include_requirements, \
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> class_name::requirements() const { \
return basicWrapperFunctionImplementationWithDefault<std::vector<std::shared_ptr<const MOBase::IPluginRequirement>>>( \
this, &class_name::requirements_Default, "requirements"); \
} \
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> class_name::requirements_Default() const { return IPlugin::requirements(); } \
bool class_name::enabledByDefault() const \
{ \
return basicWrapperFunctionImplementationWithDefault<bool>(this, &class_name::enabledByDefault_Default, "enabledByDefault"); \
} \
bool class_name::enabledByDefault_Default() const { return IPlugin::enabledByDefault(); })
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, 1)
}
/// end COMMON_I_PLUGIN_WRAPPER_DEFINITIONS
/////////////////////////////
@@ -139,10 +116,6 @@ MappingType IPluginFileMapperWrapper::mappings() const
/////////////////////////////////////
/// IPluginGame Wrapper
void IPluginGameWrapper::detectGame()
{
return basicWrapperFunctionImplementation<void>(this, "detectGame");
}
QString IPluginGameWrapper::gameName() const
{
@@ -154,11 +127,14 @@ void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettin
basicWrapperFunctionImplementation<void>(this, "initializeProfile", directory, settings);
}
std::vector<std::shared_ptr<const MOBase::ISaveGame>> IPluginGameWrapper::listSaves(QDir folder) const
QString IPluginGameWrapper::savegameExtension() const
{
// Why do I not need to hold python references here? Is it because those are wrapped
// in shared_ptr?
return basicWrapperFunctionImplementation<std::vector<std::shared_ptr<const MOBase::ISaveGame>>>(this, "listSaves", folder);
return basicWrapperFunctionImplementation<QString>(this, "savegameExtension");
}
QString IPluginGameWrapper::savegameSEExtension() const
{
return basicWrapperFunctionImplementation<QString>(this, "savegameSEExtension");
}
bool IPluginGameWrapper::isInstalled() const
@@ -301,11 +277,11 @@ QString IPluginGameWrapper::getLauncherName() const
return basicWrapperFunctionImplementation<QString>(this, "getLauncherName");
}
COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(IPluginGameWrapper, 0)
COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginGameWrapper)
std::map<std::type_index, std::any> IPluginGameWrapper::featureList() const
std::map<std::type_index, boost::any> IPluginGameWrapper::featureList() const
{
return basicWrapperFunctionImplementation<std::map<std::type_index, std::any>>(this, "_featureList");
return basicWrapperFunctionImplementation<std::map<std::type_index, boost::any>>(this, "_featureList");
}
/// end IPluginGame Wrapper
/////////////////////////////////////
@@ -314,11 +290,7 @@ std::map<std::type_index, std::any> IPluginGameWrapper::featureList() const
#define COMMON_I_PLUGIN_INSTALLER_WRAPPER_DEFINITIONS(class_name) \
unsigned int class_name::priority() const { return basicWrapperFunctionImplementation<unsigned int>(this, "priority"); } \
bool class_name::isManualInstaller() const { return basicWrapperFunctionImplementation<bool>(this, "isManualInstaller"); } \
void class_name::onInstallationStart(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) { \
basicWrapperFunctionImplementationWithDefault<void>(this, &class_name::onInstallationStart_Default, "onInstallationStart", archive, reinstallation, boost::python::ptr(currentMod)); } \
void class_name::onInstallationEnd(EInstallResult result, MOBase::IModInterface* newMod) { \
basicWrapperFunctionImplementationWithDefault<void>(this, &class_name::onInstallationEnd_Default, "onInstallationEnd", result, boost::python::ptr(newMod)); } \
bool class_name::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const { return basicWrapperFunctionImplementation<bool>(this, "isArchiveSupported", tree); }
bool class_name::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const { return basicWrapperFunctionImplementation<bool>(this, "isArchiveSupported", tree); }
/// end IPluginInstaller macro
/////////////////////////////////////
@@ -335,11 +307,11 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install(
using return_type = std::variant<
IPluginInstaller::EInstallResult,
std::shared_ptr<IFileTree>,
std::tuple<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>, QString, int>>;
std::shared_ptr<IFileTree>,
std::tuple<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>, QString, int>> ;
auto ret = basicWrapperFunctionImplementation<return_type>(this, "install", boost::ref(modName), tree, version, nexusID);
auto result = std::visit([&](auto const& t) {
return std::visit([&](auto const& t) {
using type = std::decay_t<decltype(t)>;
if constexpr (std::is_same_v<type, IPluginInstaller::EInstallResult>) {
return t;
@@ -355,9 +327,6 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install(
return std::get<0>(t);
}
}, ret);
tree = utils::clean_shared_ptr(tree);
return result;
}
/// end IPluginInstallerSimple Wrapper
@@ -379,7 +348,7 @@ std::set<QString> IPluginInstallerCustomWrapper::supportedExtensions() const
IPluginInstaller::EInstallResult IPluginInstallerCustomWrapper::install(
GuessedValue<QString> &modName, QString gameName, const QString &archiveName, const QString &version, int modID)
{
// Note: This requires far more less trouble than the "Simple" installer version since 1) there is no tree
// Note: This requires far more less trouble than the "Simple" installer version since 1) there is no tree
// and 2) there version and modId cannot be modified:
return basicWrapperFunctionImplementation<IPluginInstaller::EInstallResult>(
this, "install", boost::ref(modName), gameName, archiveName, version, modID);
+9 -28
View File
@@ -2,7 +2,6 @@
#define PROXYPLUGINWRAPPERS_H
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <ipluginfilemapper.h>
#include <iplugingame.h>
@@ -14,30 +13,18 @@
#ifndef Q_MOC_RUN
#include <boost/python.hpp>
#include <boost/preprocessor/control/expr_if.hpp>
#endif
// The wrapper for IPluginGame cannot override requirements or enabledByDefault since they're final,
// so we need to be able to exclude the declarations.
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(include_requirements) \
public: \
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS public: \
virtual bool init(MOBase::IOrganizer *moInfo) override; \
virtual QString name() const override; \
virtual QString localizedName() const override; \
virtual QString master() const override; \
virtual QString author() const override; \
virtual QString description() const override; \
virtual MOBase::VersionInfo version() const override; \
virtual QList<MOBase::PluginSetting> settings() const override; \
QString localizedName_Default() const; \
QString master_Default() const; \
BOOST_PP_EXPR_IF(include_requirements, \
virtual std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> requirements() const override; \
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> requirements_Default() const; \
virtual bool enabledByDefault() const override; \
bool enabledByDefault_Default() const;)
virtual bool isActive() const override; \
virtual QList<MOBase::PluginSetting> settings() const override;
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(1)
// Even though the base interface is not a QObject, this has to be because we have no way to pass Mod Organizer a plugin that implements multiple interfaces.
// QObject must be the first base class because moc assumes the first base class is a QObject
@@ -102,10 +89,10 @@ public:
static constexpr const char* className = "IPluginGameWrapper";
using boost::python::wrapper<MOBase::IPluginGame>::get_override;
virtual void detectGame() override;
virtual QString gameName() const override;
virtual void initializeProfile(const QDir &directory, ProfileSettings settings) const override;
virtual std::vector<std::shared_ptr<const MOBase::ISaveGame>> listSaves(QDir folder) const override;
virtual QString savegameExtension() const override;
virtual QString savegameSEExtension() const override;
virtual bool isInstalled() const override;
virtual QIcon gameIcon() const override;
virtual QDir gameDirectory() const override;
@@ -135,11 +122,11 @@ public:
virtual QString gameVersion() const override;
virtual QString getLauncherName() const override;
COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(0)
COMMON_I_PLUGIN_WRAPPER_DECLARATIONS
protected:
// Apparently, Python developers interpret an underscore in a function name as it being protected
virtual std::map<std::type_index, std::any> featureList() const override;
virtual std::map<std::type_index, boost::any> featureList() const override;
// Thankfully, the default implementation of the templated 'T *feature()' function should allow us to get away without overriding it.
};
@@ -150,13 +137,7 @@ using IPluginInstaller::parentWidget; \
using IPluginInstaller::manager; \
virtual unsigned int priority() const override; \
virtual bool isManualInstaller() const override; \
virtual void onInstallationStart(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) override; \
void onInstallationStart_Default(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) { \
return IPluginInstaller::onInstallationStart(archive, reinstallation, currentMod); } \
virtual void onInstallationEnd(EInstallResult result, MOBase::IModInterface* newMod) override; \
void onInstallationEnd_Default(EInstallResult result, MOBase::IModInterface* newMod) { \
return IPluginInstaller::onInstallationEnd(result, newMod); } \
virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const override;
virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const override;
class IPluginInstallerSimpleWrapper : public MOBase::IPluginInstallerSimple, public boost::python::wrapper<MOBase::IPluginInstallerSimple>
-74
View File
@@ -1,74 +0,0 @@
#include "pylogger.h"
#include "log.h"
#include <boost/python.hpp>
namespace bpy = boost::python;
// Small structure to hold the levels - There are copy paste from
// my Python version and I assume these will not change soon:
struct PyLogLevel {
static constexpr int CRITICAL = 50;
static constexpr int ERROR = 40;
static constexpr int WARNING = 30;
static constexpr int INFO = 20;
static constexpr int DEBUG = 10;
};
// This is the function we are going to use as our Handler .emit
// method.
void emit_function(bpy::object self, bpy::object record) {
// There are other parameters that could be used, but this is minimal for
// now (filename, line number, etc.).
const int level = bpy::extract<int>(record.attr("levelno"));
const std::wstring msg = bpy::extract<std::wstring>(bpy::str(record.attr("msg")));
switch (level) {
case PyLogLevel::CRITICAL:
case PyLogLevel::ERROR:
MOBase::log::error("{}", msg);
break;
case PyLogLevel::WARNING:
MOBase::log::warn("{}", msg);
break;
case PyLogLevel::INFO:
MOBase::log::info("{}", msg);
break;
case PyLogLevel::DEBUG:
default: // There is a "NOTSET" level in theory:
MOBase::log::debug("{}", msg);
break;
}
};
void configure_python_logging(bpy::object mobase)
{
// Most of this is dealing with actual Python objects since it is not possible
// to derive from logging.Handler in C++ using Boost.Python, and since a lot of
// this would require extra register only for this.
// Retrieve the logging module and the Handler class.
auto logging = bpy::import("logging");
auto Handler = logging.attr("Handler");
// This is ugly but that's how it's done in C Python.
auto type = (PyObject*)&PyType_Type;
// Create the "MO2Handler" python class:
auto methods = bpy::dict();
methods["emit"] = bpy::make_function(emit_function);
auto MO2Handler = bpy::call<bpy::object>(type, "LogHandler", bpy::make_tuple(Handler), methods);
// Create the default logger:
auto handler = MO2Handler();
handler.attr("setLevel")(PyLogLevel::DEBUG);
auto logger = logging.attr("getLogger")(bpy::object(mobase.attr("__name__")));
logger.attr("setLevel")(PyLogLevel::DEBUG);
logger.attr("addHandler")(handler);
// Set mobase attributes:
mobase.attr("LogHandler") = MO2Handler;
mobase.attr("logger") = logger;
}
-13
View File
@@ -1,13 +0,0 @@
#ifndef MO2_PYTHON_LOGGER_H
#define MO2_PYTHON_LOGGER_H
#include <boost/python.hpp>
/**
* @brief Configure logging for MO2 python plugin.
*
* @param mobase The mobase module.
*/
void configure_python_logging(boost::python::object mobase);
#endif
File diff suppressed because it is too large Load Diff
+4 -8
View File
@@ -10,13 +10,9 @@
class IPythonRunner {
public:
virtual QList<QObject*> load(const QString& identifier) = 0;
virtual void unload(const QString& identifier) = 0;
virtual bool isPythonInitialized() const = 0;
virtual ~IPythonRunner() { }
virtual QList<QObject*> instantiate(const QString &pluginName) = 0;
virtual bool isPythonInstalled() const = 0;
virtual bool isPythonVersionSupported() const = 0;
};
@@ -26,7 +22,7 @@ public:
#define PYDLLEXPORT Q_DECL_IMPORT
#endif // PYTHONRUNNER_LIBRARY
extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner();
extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner(MOBase::IOrganizer *moInfo, const QString &pythonDir);
-46
View File
@@ -1,46 +0,0 @@
#include "pythonutils.h"
#include <filesystem>
#include <set>
#include <QCoreApplication>
#include "log.h"
namespace utils {
void show_deprecation_warning(std::string_view name, std::string_view message, bool show_once) {
// Contains the list of filename / line number for which a deprecation warning has already been shown.
static std::set<std::pair<std::string, int>> DeprecatedLines;
// Find the caller:
auto inspect = bpy::import("inspect");
auto current_frame = inspect.attr("currentframe")();
auto callable_frame = inspect.attr("getouterframes")(current_frame, 2);
auto filename = bpy::extract<std::string>(callable_frame[-1].attr("filename"))();
auto function = bpy::extract<std::string>(callable_frame[-1].attr("function"))();
auto lineno = bpy::extract<int>(callable_frame[-1].attr("lineno"));
// Only show once if requested:
if (show_once && DeprecatedLines.contains({ filename, lineno })) {
return;
}
// Register the deprecation:
DeprecatedLines.emplace(filename, lineno);
auto path = relative(std::filesystem::path(filename), QCoreApplication::applicationDirPath().toStdWString());
// Show the message:
if (message.empty()) {
MOBase::log::warn(
"[deprecated] {} in {} [{}:{}].", name, function, path.native(), lineno);
}
else {
MOBase::log::warn(
"[deprecated] {} in {} [{}:{}]: {}", name, function, path.native(), lineno, message);
}
}
}
+11 -82
View File
@@ -3,8 +3,6 @@
#include <boost/python.hpp>
#include "error.h"
namespace utils {
namespace bpy = boost::python;
@@ -91,19 +89,19 @@ namespace utils {
using value_type = typename Container::value_type;
static void* convertible(PyObject* objPtr) {
// Check that the object can be iterated or is a sequence. There is no "clean"
// way checking that an object is iterable apparently (PyIter_Check checks that
// an object is an iterator, which is very different).
if (objPtr->ob_type->tp_iter != 0 || PySequence_Check(objPtr)) return objPtr;
if (PySequence_Check(objPtr)) return objPtr;
return nullptr;
}
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
Container* result = new (storage) Container();
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
bpy::stl_input_iterator<value_type> begin(source), end;
std::copy(begin, end, std::back_inserter(*result));
bpy::list source(bpy::handle<>(bpy::borrowed(objPtr)));
int length = bpy::len(source);
for (int i = 0; i < length; ++i) {
result->push_back(bpy::extract<value_type>(source[i]));
}
data->convertible = storage;
}
};
@@ -132,8 +130,7 @@ namespace utils {
using value_type = typename Container::value_type;
static void* convertible(PyObject* objPtr) {
// See container_from_python.
if (objPtr->ob_type->tp_iter != 0 && PySequence_Check(objPtr)) return objPtr;
if (PySequence_Check(objPtr)) return objPtr;
return nullptr;
}
@@ -141,55 +138,15 @@ namespace utils {
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
Container* result = new (storage) Container();
bpy::list source(bpy::handle<>(bpy::borrowed(objPtr)));
bpy::stl_input_iterator<value_type> begin(source), end;
std::copy(begin, end, std::inserter(*result, result->begin()));
data->convertible = storage;
}
};
template <class Optional>
struct optional_to_python {
static PyObject* convert(const Optional& optional) {
if (optional) {
return bpy::incref(bpy::object(*optional).ptr());
}
else {
return bpy::incref(Py_None);
}
}
};
template <class Optional>
struct optional_from_python {
using value_type = typename Optional::value_type;
static void* convertible(PyObject* objPtr) {
if (objPtr == Py_None) {
return objPtr;
}
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
return bpy::extract<value_type>(source).check() ? objPtr : nullptr;
}
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
void* storage = ((bpy::converter::rvalue_from_python_storage<Optional>*)data)->storage.bytes;
Optional* result = new (storage) Optional();
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
if (!source.is_none()) {
*result = bpy::extract<value_type>(source)();
int length = bpy::len(source);
for (int i = 0; i < length; ++i) {
result->insert(bpy::extract<value_type>(source[i]));
}
data->convertible = storage;
}
};
/**
* @brief Register from and to python converters for (at least) map, unordered_map and QMap.
*
@@ -238,34 +195,6 @@ namespace utils {
, bpy::type_id<Container>());
};
/**
* @brief Register from and to python converters for optional.
*
* @tparam T The optional type (std::optional<X> or boost::optional<X>).
*/
template <class Optional>
void register_optional() {
bpy::to_python_converter<Optional, optional_to_python<Optional>>();
bpy::converter::registry::push_back(
&optional_from_python<Optional>::convertible
, &optional_from_python<Optional>::construct
, bpy::type_id<Optional>());
};
/**
* @brief Show a deprecation warning.
*
* This methods will print a warning in MO2 log containing the location of the call to
* the deprecated function. If show_once is true, the deprecation warning will only be
* logged the first time the function is called at this location.
*
* @param name Name of the deprecated function.
* @param message Deprecation message.
* @param show_once Only show the message once per call location.
*/
void show_deprecation_warning(std::string_view name, std::string_view message = "", bool show_once = true);
}
#endif
+2 -32
View File
@@ -19,10 +19,8 @@ namespace details {
*/
template <class ReturnType, class WrapperTypePtr, class Fn, class... Args>
ReturnType wrapperFunctionImplementation(WrapperTypePtr wrapper, bool apiTransfer, Fn fn, boost::python::object* objPtr, const char *methodName, Args... args) {
boost::python::override implementation = [&]() {
GILock lock;
return wrapper->get_override(methodName);
}();
GILock lock;
boost::python::override implementation = wrapper->get_override(methodName);
if (!implementation) {
if constexpr (std::is_same_v<Fn, std::nullptr_t>) {
throw pyexcept::MissingImplementation(wrapper->className, methodName);
@@ -31,8 +29,6 @@ namespace details {
return std::invoke(fn, wrapper, args...);
}
}
GILock lock;
try {
boost::python::object result = implementation(args...);
if (objPtr) {
@@ -143,30 +139,4 @@ ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper,
return details::wrapperFunctionImplementation<ReturnType>(wrapper, false, fn, nullptr, methodName, args...);
}
/**
* @brief Call the given method on the wrapper with the given arguments, with proper
* exception handling, and store the intermediate result in the given python object,
* falling back to the given function if the method does not exist.
*
* @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly
* available `className` attribute.
* @param fn The function to call if the method does not exists.
* @param ref Python object to which the result of `get_override()` should be stored.
* @param methodName The name of the method.
* @param args... Arguments for the method.
*
* Note: `fn` does not have to be a member-function of `wrapper` but `std::invoke(fn, wrapper, args...)` must be valid.
*
* @return the result of calling the given Python method on the wrapper.
*
* @throw pyexcept::PythonError if an error occurs while executing the python method.
* @throw pyexecpt::UnknownException if an unknown error occurs.
*/
template <class ReturnType, class WrapperType, class Fn, class... Args>
ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, boost::python::object& ref, const char* methodName, Args... args)
{
return details::wrapperFunctionImplementation<ReturnType>(wrapper, false, fn, &ref, methodName, args...);
}
#endif // PYTHONWRAPPERUTILITIES_H
-125
View File
@@ -1,125 +0,0 @@
#ifndef PYTHONRUNNER_SHARED_PTR_CONVERTER_H
#define PYTHONRUNNER_SHARED_PTR_CONVERTER_H
#include <boost/python.hpp>
#include "error.h"
#include "gilock.h"
namespace utils {
// Shared pointers are handled in a special way by Boost.Python since they hold
// the wrapped Python object and only release it when the ref counter of the shared
// ptr drops to 0 using shared_ptr_deleter.
//
// Unfortunately for us, this will happen outside of the Python proxy for some objects
// and thus without the GIL lock, making everything crash, so we need a custom deleter
// that holds the GIL while releasing the lock.
//
// Note that this is only useful for Python -> C++ conversion, and without this, Boost
// will automatically wrapped the pointer. The C++ -> Python conversion is handled
// separately by boost::python::register_ptr_to_python.
//
// This is an open Boost.Python problem: https://github.com/boostorg/python/pull/11
template <class SharedPtr>
struct shared_ptr_from_python;
namespace details {
struct shared_ptr_deleter_with_gil_lock : boost::python::converter::shared_ptr_deleter {
using shared_ptr_deleter::shared_ptr_deleter;
void operator()(void const* o) {
GILock lock;
shared_ptr_deleter::operator()(o);
}
};
template <class SharedPtr>
struct shared_ptr_void;
template <class T>
struct shared_ptr_void<std::shared_ptr<T>> { using type = std::shared_ptr<void>; };
template <class T>
struct shared_ptr_void<boost::shared_ptr<T>> { using type = boost::shared_ptr<void>; };
template <class SharedPtr>
using shared_ptr_void_t = typename shared_ptr_void<SharedPtr>::type;
}
template <class SharedPtr>
struct shared_ptr_from_python
{
using T = typename SharedPtr::element_type;
shared_ptr_from_python()
{
using namespace boost::python;
converter::registry::insert(&convertible, &construct, type_id<SharedPtr>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
, &converter::expected_from_python_type_direct<T>::get_pytype
#endif
);
}
private:
static void* convertible(PyObject* p)
{
if (p == Py_None)
return p;
return boost::python::converter::get_lvalue_from_python(p, boost::python::converter::registered<T>::converters);
}
static void construct(PyObject* source, boost::python::converter::rvalue_from_python_stage1_data* data)
{
using namespace boost::python;
void* const storage = ((converter::rvalue_from_python_storage<SharedPtr>*)data)->storage.bytes;
// Deal with the "None" case.
if (data->convertible == source)
new (storage) SharedPtr();
else
{
details::shared_ptr_void_t<SharedPtr> hold_convertible_ref_count(
(void*)0, details::shared_ptr_deleter_with_gil_lock(handle<>(borrowed(source))));
// use aliasing constructor
new (storage) SharedPtr(hold_convertible_ref_count,
static_cast<T*>(data->convertible));
}
data->convertible = storage;
}
};
// release the bpy::object associated with the deleter of the given shared_ptr,
// if the given shared_ptr has a Boost.Python deleter
//
// this should only be used when returning from Python objects that have been created
// on the C++ side, e.g. if IFileTree.createOrphanTree() from Python and then return
// the tree
//
// for reason yet to be known, Boost.Python had a custom deleter in this case that tries
// to delete the bpy::object and fails, so we have to release the object manually
//
template <class SharedPtr>
SharedPtr clean_shared_ptr(SharedPtr&& ptr) {
if (auto* d = get_deleter<boost::python::converter::shared_ptr_deleter>(ptr); d != nullptr) {
// we cannot do a proper reset() here, even with the GIL lock, for unknown reason,
// so we only release
//
// this might create lost references to Python object but this should not happen
// too often so hopefully it's not a big issue
//
d->owner.release();
}
return ptr;
}
}
#endif

Some files were not shown because too many files have changed in this diff Show More