From 902024e5089088ab9ff9f958693ac1491c4e098e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Apr 2018 17:25:12 +0100 Subject: [PATCH] Add Game Features support --- src/runner/CMakeLists.txt | 1 + src/runner/gamefeatureswrappers.cpp | 391 ++++++++++++++++++++++++++++ src/runner/gamefeatureswrappers.h | 85 ++++++ src/runner/proxypluginwrappers.cpp | 41 ++- src/runner/proxypluginwrappers.h | 3 +- src/runner/pycatch.h | 11 + src/runner/pythonrunner.cpp | 56 +++- src/runner/uibasewrappers.h | 17 ++ 8 files changed, 592 insertions(+), 13 deletions(-) create mode 100644 src/runner/gamefeatureswrappers.cpp create mode 100644 src/runner/gamefeatureswrappers.h create mode 100644 src/runner/pycatch.h diff --git a/src/runner/CMakeLists.txt b/src/runner/CMakeLists.txt index 060055b..8750dd7 100644 --- a/src/runner/CMakeLists.txt +++ b/src/runner/CMakeLists.txt @@ -34,6 +34,7 @@ SET(project_path "${default_project_path}" CACHE PATH "path to the other mo proj SET(lib_path "${project_path}/../../install/libs") INCLUDE_DIRECTORIES(${project_path}/uibase/src + ${project_path}/game_features/src ${PYTHON_ROOT}/Include ${SIP_ROOT} ${PYTHON_ROOT}/PC) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp new file mode 100644 index 0000000..b887e02 --- /dev/null +++ b/src/runner/gamefeatureswrappers.cpp @@ -0,0 +1,391 @@ +#include "gamefeatureswrappers.h" + +#include + +#include + +#include +#include +#include +#include + +#include "gilock.h" +#include "pycatch.h" + +///////////////////////////// +/// BSAInvalidation Wrapper + + +bool BSAInvalidationWrapper::isInvalidationBSA(const QString &bsaName) +{ + GILock lock; + + try { + return this->get_override("isInvalidationBSA")(bsaName); + } PYCATCH; +} + +void BSAInvalidationWrapper::deactivate(MOBase::IProfile *profile) +{ + GILock lock; + + try { + this->get_override("deactivate")(boost::python::ptr(profile)); + } PYCATCH; +} + +void BSAInvalidationWrapper::activate(MOBase::IProfile *profile) +{ + GILock lock; + + try { + this->get_override("activate")(boost::python::ptr(profile)); + } PYCATCH; +} +/// end BSAInvalidation Wrapper +///////////////////////////// +/// DataArchives Wrapper + + +QStringList DataArchivesWrapper::vanillaArchives() const +{ + GILock lock; + + try { + return this->get_override("vanillaArchives")(); + } PYCATCH; +} + +QStringList DataArchivesWrapper::archives(const MOBase::IProfile *profile) const +{ + GILock lock; + + try { + return this->get_override("archives")(boost::python::ptr(profile)); + } PYCATCH; +} + +void DataArchivesWrapper::addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) +{ + GILock lock; + + try { + this->get_override("addArchive")(boost::python::ptr(profile), index, archiveName); + } PYCATCH; +} + +void DataArchivesWrapper::removeArchive(MOBase::IProfile * profile, const QString & archiveName) +{ + GILock lock; + + try { + this->get_override("removeArchive")(boost::python::ptr(profile), archiveName); + } PYCATCH; +} +/// end DataArchives Wrapper +///////////////////////////// +/// GamePlugins Wrapper + + +void GamePluginsWrapper::writePluginLists(const MOBase::IPluginList * pluginList) +{ + GILock lock; + + try { + this->get_override("writePluginLists")(boost::python::ptr(pluginList)); + } PYCATCH; +} + +void GamePluginsWrapper::readPluginLists(MOBase::IPluginList * pluginList) +{ + GILock lock; + + try { + this->get_override("readPluginLists")(boost::python::ptr(pluginList)); + } PYCATCH; +} +/// end GamePlugins Wrapper +///////////////////////////// +/// LocalSavegames Wrapper + + +MappingType LocalSavegamesWrapper::mappings(const QDir & profileSaveDir) const +{ + GILock lock; + + try { + return this->get_override("mappings")(profileSaveDir); + } PYCATCH; +} + +void LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) +{ + GILock lock; + + try { + this->get_override("prepareProfile")(boost::python::ptr(profile)); + } PYCATCH; +} +/// end LocalSavegames Wrapper +///////////////////////////// +/// SaveGameInfo Wrapper + + +MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("getSaveGameInfo")(file); + } PYCATCH; +} + +SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("getMissingAssets")(file); + } PYCATCH; +} + +MOBase::ISaveGameInfoWidget * SaveGameInfoWrapper::getSaveGameWidget(QWidget * parent) const +{ + qCritical("Calling method with unimplemented from_python converter."); + + GILock lock; + + try { + return this->get_override("getSaveGameWidget")(boost::python::ptr(parent)); + } PYCATCH; +} + +bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("hasScriptExtenderSave")(file); + } PYCATCH; +} +/// end SaveGameInfo Wrapper +///////////////////////////// +/// ScriptExtender Wrapper + +QString ScriptExtenderWrapper::BinaryName() const +{ + GILock lock; + + try { + return this->get_override("BinaryName")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::PluginPath() const +{ + GILock lock; + + try { + return this->get_override("PluginPath")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::loaderName() const +{ + GILock lock; + + try { + return this->get_override("loaderName")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::loaderPath() const +{ + GILock lock; + + try { + return this->get_override("loaderPath")(); + } PYCATCH; +} + +QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const +{ + GILock lock; + + try { + return this->get_override("saveGameAttachmentExtensions")(); + } PYCATCH; +} + +bool ScriptExtenderWrapper::isInstalled() const +{ + GILock lock; + + try { + return this->get_override("isInstalled")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::getExtenderVersion() const +{ + GILock lock; + + try { + return this->get_override("getExtenderVersion")(); + } PYCATCH; +} + +WORD ScriptExtenderWrapper::getArch() const +{ + GILock lock; + + try { + return this->get_override("getArch")(); + } PYCATCH; +} + +/// end ScriptExtender Wrapper +///////////////////////////// +/// UnmanagedMods Wrapper + + +QStringList UnmanagedModsWrapper::mods(bool onlyOfficial) const +{ + GILock lock; + + try { + return this->get_override("mods")(onlyOfficial); + } PYCATCH; +} + +QString UnmanagedModsWrapper::displayName(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("displayName")(modName); + } PYCATCH; +} + +QFileInfo UnmanagedModsWrapper::referenceFile(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("referenceFile")(modName); + } PYCATCH; +} + +QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("secondaryFiles")(modName); + } PYCATCH; +} +/// end UnmanagedMods Wrapper +///////////////////////////// + +template +void insertGameFeature(std::map &map, const boost::python::object &pyObject) +{ + map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); +} + +game_features_map_from_python::game_features_map_from_python() +{ + boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id>()); +} + +void * game_features_map_from_python::convertible(PyObject * objPtr) +{ + return PyDict_Check(objPtr) ? objPtr : nullptr; +} + +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>*)data)->storage.bytes; + std::map *result = new (storage) std::map(); + boost::python::dict source(boost::python::handle<>(boost::python::borrowed(objPtr))); + boost::python::list keys = source.keys(); + int len = boost::python::len(keys); + for (int i = 0; i < len; ++i) + { + boost::python::object pyKey = keys[i]; + // pyKey should be a Boost.Python.class corresponding to a game feature. + std::string className = boost::python::extract(pyKey.attr("__name__"))(); + if (className == "BSAInvalidation") + insertGameFeature(*result, source[pyKey]); + else if (className == "DataArchives") + insertGameFeature(*result, source[pyKey]); + else if (className == "GamePlugins") + insertGameFeature(*result, source[pyKey]); + else if (className == "LocalSavegames") + insertGameFeature(*result, source[pyKey]); + else if (className == "SaveGameInfo") + insertGameFeature(*result, source[pyKey]); + else if (className == "ScriptExtender") + insertGameFeature(*result, source[pyKey]); + else if (className == "UnmanagedMods") + insertGameFeature(*result, source[pyKey]); + } + + data->convertible = storage; +} + +void registerGameFeaturesPythonConverters() +{ + namespace bpy = boost::python; + + game_features_map_from_python(); + + // Features require defs for all methods as Python can access C++ features + bpy::class_("BSAInvalidation") + .def("isInvalidationBSA", bpy::pure_virtual(&BSAInvalidation::isInvalidationBSA)) + .def("deactivate", bpy::pure_virtual(&BSAInvalidation::deactivate)) + .def("activate", bpy::pure_virtual(&BSAInvalidation::activate)) + ; + + bpy::class_("DataArchives") + .def("vanillaArchives", bpy::pure_virtual(&DataArchives::vanillaArchives)) + .def("archives", bpy::pure_virtual(&DataArchives::archives)) + .def("addArchive", bpy::pure_virtual(&DataArchives::addArchive)) + .def("removeArchive", bpy::pure_virtual(&DataArchives::removeArchive)) + ; + + bpy::class_("GamePlugins") + .def("writePluginLists", bpy::pure_virtual(&GamePlugins::writePluginLists)) + .def("readPluginLists", bpy::pure_virtual(&GamePlugins::readPluginLists)) + ; + + bpy::class_("LocalSavegames") + .def("mappings", bpy::pure_virtual(&LocalSavegames::mappings)) + .def("prepareProfile", bpy::pure_virtual(&LocalSavegames::prepareProfile)) + ; + + bpy::class_("SaveGameInfo") + .def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy()) + .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets)) + .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy()) + .def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave)) + ; + + bpy::class_("ScriptExtender") + .def("BinaryName", bpy::pure_virtual(&ScriptExtender::BinaryName)) + .def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath)) + .def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName)) + .def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath)) + .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)) + ; + + bpy::class_("UnmanagedMods") + .def("mods", bpy::pure_virtual(&UnmanagedMods::mods)) + .def("displayName", bpy::pure_virtual(&UnmanagedMods::displayName)) + .def("referenceFile", bpy::pure_virtual(&UnmanagedMods::referenceFile)) + .def("secondaryFiles", bpy::pure_virtual(&UnmanagedMods::secondaryFiles)) + ; +} diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h new file mode 100644 index 0000000..889889a --- /dev/null +++ b/src/runner/gamefeatureswrappers.h @@ -0,0 +1,85 @@ +#ifndef GAMEFEATURESWRAPPERS_H +#define GAMEFEATURESWRAPPERS_H + +#include +#include +#include +#include +#include +#include +#include + +// this might need turning off if Q_MOC_RUN is defined +#include + +///////////////////////////// +/// Wrapper declarations + +class BSAInvalidationWrapper : public BSAInvalidation, public boost::python::wrapper +{ + virtual bool isInvalidationBSA(const QString &bsaName) override; + virtual void deactivate(MOBase::IProfile *profile) override; + virtual void activate(MOBase::IProfile *profile) override; +}; + +class DataArchivesWrapper : public DataArchives, public boost::python::wrapper +{ + virtual QStringList vanillaArchives() const override; + virtual QStringList archives(const MOBase::IProfile *profile) const override; + virtual void addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) override; + virtual void removeArchive(MOBase::IProfile *profile, const QString &archiveName) override; +}; + +class GamePluginsWrapper : public GamePlugins, public boost::python::wrapper +{ + virtual void writePluginLists(const MOBase::IPluginList *pluginList) override; + virtual void readPluginLists(MOBase::IPluginList *pluginList) override; +}; + +class LocalSavegamesWrapper : public LocalSavegames, public boost::python::wrapper +{ + virtual MappingType mappings(const QDir &profileSaveDir) const override; + virtual void prepareProfile(MOBase::IProfile *profile) override; +}; + +class SaveGameInfoWrapper : public SaveGameInfo, public boost::python::wrapper +{ + 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; +}; + +class ScriptExtenderWrapper : public ScriptExtender, public boost::python::wrapper +{ + virtual QString BinaryName() const override; + virtual QString PluginPath() const override; + virtual QString loaderName() const override; + virtual QString loaderPath() const override; + virtual QStringList saveGameAttachmentExtensions() const override; + virtual bool isInstalled() const override; + virtual QString getExtenderVersion() const override; + virtual WORD getArch() const override; +}; + +class UnmanagedModsWrapper : public UnmanagedMods, public boost::python::wrapper +{ + virtual QStringList mods(bool onlyOfficial) const override; + virtual QString displayName(const QString &modName) const override; + virtual QFileInfo referenceFile(const QString &modName) const override; + virtual QStringList secondaryFiles(const QString &modName) const override; +}; + +/// end Wrapper declarations +///////////////////////////// + +struct game_features_map_from_python +{ + game_features_map_from_python(); + static void *convertible(PyObject *objPtr); + static void construct(PyObject *objPtr, boost::python::converter::rvalue_from_python_stage1_data *data); +}; + +void registerGameFeaturesPythonConverters(); + +#endif // GAMEFEATURESWRAPPERS_H diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 7518151..da807aa 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -1,9 +1,9 @@ #include "proxypluginwrappers.h" -#include -#include "error.h" + #include "gilock.h" #include #include +#include "pycatch.h" #include "sipApiAccess.h" namespace boost @@ -23,9 +23,6 @@ namespace boost using namespace MOBase; -#define PYCATCH catch (const boost::python::error_already_set &) { reportPythonError(); throw MyException("unhandled exception"); }\ - catch (...) { throw MyException("An unknown exception was thrown in python code"); } - #define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) \ bool class_name::init(MOBase::IOrganizer *moInfo) \ @@ -153,6 +150,7 @@ MappingType IPluginFileMapperWrapper::mappings() const QString IPluginGameWrapper::gameName() const { + GILock lock; try { return this->get_override("gameName")(); } PYCATCH; @@ -160,6 +158,7 @@ QString IPluginGameWrapper::gameName() const void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettings settings) const { + GILock lock; try { this->get_override("initializeProfile")(directory, settings); } PYCATCH; @@ -167,6 +166,7 @@ void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettin QString IPluginGameWrapper::savegameExtension() const { + GILock lock; try { return this->get_override("savegameExtension")(); } PYCATCH; @@ -174,6 +174,7 @@ QString IPluginGameWrapper::savegameExtension() const QString IPluginGameWrapper::savegameSEExtension() const { + GILock lock; try { return this->get_override("savegameSEExtension")(); } PYCATCH; @@ -181,6 +182,7 @@ QString IPluginGameWrapper::savegameSEExtension() const bool IPluginGameWrapper::isInstalled() const { + GILock lock; try { return this->get_override("isInstalled")(); } PYCATCH; @@ -188,6 +190,7 @@ bool IPluginGameWrapper::isInstalled() const QIcon IPluginGameWrapper::gameIcon() const { + GILock lock; try { return this->get_override("gameIcon")(); } PYCATCH; @@ -195,6 +198,7 @@ QIcon IPluginGameWrapper::gameIcon() const QDir IPluginGameWrapper::gameDirectory() const { + GILock lock; try { return this->get_override("gameDirectory")(); } PYCATCH; @@ -202,6 +206,7 @@ QDir IPluginGameWrapper::gameDirectory() const QDir IPluginGameWrapper::dataDirectory() const { + GILock lock; try { return this->get_override("dataDirectory")(); } PYCATCH; @@ -209,6 +214,7 @@ QDir IPluginGameWrapper::dataDirectory() const void IPluginGameWrapper::setGamePath(const QString & path) { + GILock lock; try { this->get_override("setGamePath")(path); } PYCATCH; @@ -216,6 +222,7 @@ void IPluginGameWrapper::setGamePath(const QString & path) QDir IPluginGameWrapper::documentsDirectory() const { + GILock lock; try { return this->get_override("documentsDirectory")(); } PYCATCH; @@ -223,6 +230,7 @@ QDir IPluginGameWrapper::documentsDirectory() const QDir IPluginGameWrapper::savesDirectory() const { + GILock lock; try { return this->get_override("savesDirectory")(); } PYCATCH; @@ -230,6 +238,7 @@ QDir IPluginGameWrapper::savesDirectory() const QList IPluginGameWrapper::executables() const { + GILock lock; try { return this->get_override("executables")(); } PYCATCH; @@ -237,6 +246,7 @@ QList IPluginGameWrapper::executables() const QString IPluginGameWrapper::steamAPPId() const { + GILock lock; try { return this->get_override("steamAPPId")(); } PYCATCH; @@ -244,6 +254,7 @@ QString IPluginGameWrapper::steamAPPId() const QStringList IPluginGameWrapper::primaryPlugins() const { + GILock lock; try { return this->get_override("primaryPlugins")(); } PYCATCH; @@ -251,6 +262,7 @@ QStringList IPluginGameWrapper::primaryPlugins() const QStringList IPluginGameWrapper::gameVariants() const { + GILock lock; try { return this->get_override("gameVariants")(); } PYCATCH; @@ -258,6 +270,7 @@ QStringList IPluginGameWrapper::gameVariants() const void IPluginGameWrapper::setGameVariant(const QString & variant) { + GILock lock; try { this->get_override("setGameVariant")(variant); } PYCATCH; @@ -265,6 +278,7 @@ void IPluginGameWrapper::setGameVariant(const QString & variant) QString IPluginGameWrapper::binaryName() const { + GILock lock; try { return this->get_override("binaryName")(); } PYCATCH; @@ -272,6 +286,7 @@ QString IPluginGameWrapper::binaryName() const QString IPluginGameWrapper::gameShortName() const { + GILock lock; try { return this->get_override("gameShortName")(); } PYCATCH; @@ -279,6 +294,7 @@ QString IPluginGameWrapper::gameShortName() const QStringList IPluginGameWrapper::validShortNames() const { + GILock lock; try { return this->get_override("validShortNames")(); } PYCATCH; @@ -286,6 +302,7 @@ QStringList IPluginGameWrapper::validShortNames() const QString IPluginGameWrapper::gameNexusName() const { + GILock lock; try { return this->get_override("gameNexusName")(); } PYCATCH; @@ -293,6 +310,7 @@ QString IPluginGameWrapper::gameNexusName() const QStringList IPluginGameWrapper::iniFiles() const { + GILock lock; try { return this->get_override("iniFiles")(); } PYCATCH; @@ -300,6 +318,7 @@ QStringList IPluginGameWrapper::iniFiles() const QStringList IPluginGameWrapper::DLCPlugins() const { + GILock lock; try { return this->get_override("DLCPlugins")(); } PYCATCH; @@ -307,6 +326,7 @@ QStringList IPluginGameWrapper::DLCPlugins() const QStringList IPluginGameWrapper::CCPlugins() const { + GILock lock; try { return this->get_override("CCPlugins")(); } PYCATCH; @@ -314,13 +334,15 @@ QStringList IPluginGameWrapper::CCPlugins() const IPluginGame::LoadOrderMechanism IPluginGameWrapper::loadOrderMechanism() const { + GILock lock; try { - return this->get_override("loadorderMechanism")(); + return this->get_override("loadOrderMechanism")(); } PYCATCH; } IPluginGame::SortMechanism IPluginGameWrapper::sortMechanism() const { + GILock lock; try { return this->get_override("sortMechanism")(); } PYCATCH; @@ -328,6 +350,7 @@ IPluginGame::SortMechanism IPluginGameWrapper::sortMechanism() const int IPluginGameWrapper::nexusModOrganizerID() const { + GILock lock; try { return this->get_override("nexusModOrganizerID")(); } PYCATCH; @@ -335,6 +358,7 @@ int IPluginGameWrapper::nexusModOrganizerID() const int IPluginGameWrapper::nexusGameID() const { + GILock lock; try { return this->get_override("nexusGameID")(); } PYCATCH; @@ -342,6 +366,7 @@ int IPluginGameWrapper::nexusGameID() const bool IPluginGameWrapper::looksValid(QDir const & dir) const { + GILock lock; try { return this->get_override("looksValid")(dir); } PYCATCH; @@ -349,6 +374,7 @@ bool IPluginGameWrapper::looksValid(QDir const & dir) const QString IPluginGameWrapper::gameVersion() const { + GILock lock; try { return this->get_override("gameVersion")(); } PYCATCH; @@ -356,6 +382,7 @@ QString IPluginGameWrapper::gameVersion() const QString IPluginGameWrapper::getLauncherName() const { + GILock lock; try { return this->get_override("getLauncherName")(); } PYCATCH; @@ -365,7 +392,7 @@ COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginGameWrapper) std::map IPluginGameWrapper::featureList() const { - qCritical("Calling unproxied method IPluginGameWrapper::featureList()"); + GILock lock; try { return this->get_override("_featureList")(); } PYCATCH; diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 322b316..12e6829 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -108,11 +108,10 @@ public: COMMON_I_PLUGIN_WRAPPER_DECLARATIONS protected: - - // TODO: implementing converters for this is required as otherwise Mod Organizer will crash on load when the game is being managed by this plugin // Apparently, Python developers interpret an underscore in a function name as it being protected virtual std::map featureList() const override; + // Thankfully, the default implementation of the templated 'T *feature()' function should allow us to get away without overriding it. }; diff --git a/src/runner/pycatch.h b/src/runner/pycatch.h new file mode 100644 index 0000000..e74ce4d --- /dev/null +++ b/src/runner/pycatch.h @@ -0,0 +1,11 @@ +#ifndef PYCATCH_H +#define PYCATCH_H + +#include + +#include "error.h" + +#define PYCATCH catch (const boost::python::error_already_set &) { reportPythonError(); throw MOBase::MyException("unhandled exception"); }\ + catch (...) { throw MOBase::MyException("An unknown exception was thrown in python code"); } + +#endif // PYCATCH_H diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 1f1cbc2..d987f91 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -9,6 +9,7 @@ #include #include "uibasewrappers.h" #include "proxypluginwrappers.h" +#include "gamefeatureswrappers.h" #include "sipApiAccess.h" #include @@ -72,7 +73,11 @@ namespace bpy = boost::python; struct QString_to_python_str { static PyObject *convert(const QString &str) { - return bpy::incref(bpy::object(str.toUtf8().constData()).ptr()); + // 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(str.toUtf8().constData()); + if (PyString_Check(pyStr.ptr())) + pyStr = pyStr.attr("decode")("utf-8"); + return bpy::incref(pyStr.ptr()); } }; @@ -334,6 +339,26 @@ struct QList_from_python_obj }; +template +struct std_vector_to_python_list +{ + static PyObject *convert(const std::vector &vector) + { + bpy::list pyList; + + try { + for (const T &item : vector) + pyList.append(item); + } + catch (const bpy::error_already_set&) { + reportPythonError(); + } + + return bpy::incref(pyList.ptr()); + } +}; + + template struct std_vector_from_python_obj { @@ -825,6 +850,16 @@ BOOST_PYTHON_MODULE(mobase) .def("isCustom", &ExecutableInfo::isCustom) ; + bpy::class_("ISaveGame") + .def("getFilename", bpy::pure_virtual(&ISaveGame::getFilename)) + .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) + .def("getSaveGroupIdentifier", bpy::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) + .def("allFiles", bpy::pure_virtual(&ISaveGame::allFiles)) + .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) + ; + + // TODO: ISaveGameInfoWidget bindings + Functor1_converter(); Functor1_converter(); Functor1_converter(); @@ -1013,8 +1048,6 @@ BOOST_PYTHON_MODULE(mobase) .def_readwrite("createTarget", &Mapping::createTarget) ; - std_vector_from_python_obj(); - bpy::class_("IPluginFileMapper") .def("mappings", bpy::pure_virtual(&MOBase::IPluginFileMapper::mappings)) ; @@ -1083,6 +1116,14 @@ BOOST_PYTHON_MODULE(mobase) .def("isActive", bpy::pure_virtual(&MOBase::IPluginGame::isActive)) .def("settings", bpy::pure_virtual(&MOBase::IPluginGame::settings)) + // The syntax has to differ slightly from C++ because these are templated + .def("featureBSAInvalidation", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureDataArchives", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureGamePlugins", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureLocalSavegames", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureSaveGameInfo", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureScriptExtender", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureUnmanagedMods", &MOBase::IPluginGame::feature, bpy::return_value_policy()) ; bpy::enum_("InstallResult") @@ -1120,13 +1161,20 @@ BOOST_PYTHON_MODULE(mobase) bpy::to_python_converter, QList_to_python_list>(); QList_from_python_obj(); - QList_to_python_list(); + bpy::to_python_converter, + QList_to_python_list>(); QMap_converters(); + QMap_converters(); std_vector_from_python_obj(); + std_vector_from_python_obj(); + bpy::to_python_converter, + std_vector_to_python_list>(); stdset_from_python_list(); + + registerGameFeaturesPythonConverters(); } diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 75db982..36f1c0c 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -21,8 +21,12 @@ #include #include #include +#include +#include + #include "error.h" #include "gilock.h" +#include "pycatch.h" extern MOBase::IOrganizer *s_Organizer; @@ -434,4 +438,17 @@ struct IModListWrapper: MOBase::IModList, boost::python::wrapper +{ +public: + virtual QString getFilename() const override { try { return this->get_override("getFilename")(); } PYCATCH }; + virtual QDateTime getCreationTime() const override { try { return this->get_override("getCreationTime")(); } PYCATCH }; + virtual QString getSaveGroupIdentifier() const override { try { return this->get_override("getSaveGroupIdentifier")(); } PYCATCH }; + virtual QStringList allFiles() const override { try { return this->get_override("allFiles")(); } PYCATCH }; + virtual bool hasScriptExtenderFile() const override { try { return this->get_override("hasScriptExtenderFile")(); } PYCATCH }; +}; + + #endif // UIBASEWRAPPERS_H