#include "PluginLoader.h" #include <QDebug> #include <vector> #include <filesystem> #include <sstream> #ifdef _WIN32 #include <stdio.h> #include <tchar.h> #include <string.h> #include <atlbase.h> #include <QDebug> #include <QFileInfo> #define MAX_KEY_LENGTH 255 #define MAX_VALUE_NAME 16383 std::vector<std::string> QueryKey(HKEY hKey, std::string path) { //See https://docs.microsoft.com/en-us/windows/desktop/sysinfo/enumerating-registry-subkeys std::vector<std::string> list; TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name DWORD cchClassName = MAX_PATH; // size of class string DWORD cSubKeys=0; // number of subkeys DWORD cbMaxSubKey; // longest subkey size DWORD cchMaxClass; // longest class string DWORD cValues; // number of values for key DWORD cchMaxValue; // longest value name DWORD cbMaxValueData; // longest value data DWORD cbSecurityDescriptor; // size of security descriptor FILETIME ftLastWriteTime; // last write time DWORD i, retCode; TCHAR achValue[MAX_VALUE_NAME]; DWORD cchValue = MAX_VALUE_NAME; // Get the class name and the value count. retCode = RegQueryInfoKey( hKey, // key handle achClass, // buffer for class name &cchClassName, // size of class string NULL, // reserved &cSubKeys, // number of subkeys &cbMaxSubKey, // longest subkey size &cchMaxClass, // longest class string &cValues, // number of values for this key &cchMaxValue, // longest value name &cbMaxValueData, // longest value data &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time // Enumerate the key values. if (cValues) { //printf( "\nNumber of values: %d\n", cValues); for (i=0, retCode=ERROR_SUCCESS; i<cValues; i++) { cchValue = MAX_VALUE_NAME; achValue[0] = '\0'; retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL); if (retCode == ERROR_SUCCESS ) { CRegKey regKey; CHAR szBuffer[512]; ULONG dwBufferSize = sizeof(szBuffer); if(ERROR_SUCCESS != regKey.Open(HKEY_LOCAL_MACHINE, path.c_str())) { qWarning() << "Error opening registry path " << path.c_str(); regKey.Close(); } if( ERROR_SUCCESS != regKey.QueryStringValue(achValue,szBuffer,&dwBufferSize)) { qWarning() << "Error opening registry value " << achValue; regKey.Close(); } std::string fp = szBuffer; std::replace( fp.begin(), fp.end(), '\\', '/'); list.push_back(fp); } } } return list; } #endif std::vector<std::string> PluginLoader::queryRegistryBehaviors(std::string path) { std::vector<std::string> list; #ifdef _WIN32 HKEY hTestKey; if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT(path.c_str()), 0, KEY_READ, &hTestKey) == ERROR_SUCCESS) { list = QueryKey(hTestKey, path); } RegCloseKey(hTestKey); #endif return list; } #ifdef _WIN32 const char * WinGetEnv(const char * name) { const DWORD buffSize = 65535; static char buffer[buffSize]; if (GetEnvironmentVariableA(name, buffer, buffSize)) { return buffer; } else { return 0; } } bool WinSetEnv(const char* name, const char* toWhat){ return SetEnvironmentVariableA(name, toWhat); } #endif const char* PluginLoader::addDllPath(std::string f) { //Get the directory of the DLL/*.so and add it to the PATH env variable. //This way dependencies can be shipped in the same directory #ifdef _WIN32 QFileInfo finf(f.c_str()); //rather than the buggy _getenv: https://docs.microsoft.com/de-de/windows/desktop/api/winbase/nf-winbase-getenvironmentvariable auto old_path = WinGetEnv("PATH"); auto path = std::ostringstream(); if(old_path){ path << old_path << ";" << finf.absolutePath().toStdString().c_str(); WinSetEnv("PATH", path.str().c_str()); }else{ qWarning() << "Failed to get and modify PATH enviromental variable."; } return old_path; #endif return ""; } void PluginLoader::delDllPath(const char* oldPath){ //reset path. We don't want some weird cross-effects #ifdef _WIN32 if(oldPath){ WinSetEnv("PATH", oldPath); } #endif } bool endsWith(std::string value, std::string ending) { std::transform(value.begin(), value.end(), value.begin(), ::tolower); std::transform(ending.begin(), ending.end(), ending.begin(), ::tolower); if (ending.size() > value.size()) return false; return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } bool validSuffix(std::string f, std::string suffix){ return (endsWith(f,suffix+".dll") || endsWith(f,suffix+".so")); } std::vector<std::string> PluginLoader::searchDirectoriesForPlugins(std::vector<std::string> list, std::string suffix){ //Search directories std::vector<std::string> filesFromFolders; for (auto f: list) { std::string file = f; try { if (!file.empty() && file[file.size() - 1] == '/') { for (auto& p : std::filesystem::directory_iterator(file)) { std::string s = p.path().string(); if(validSuffix(s, suffix)) filesFromFolders.push_back(s); } } else { if(validSuffix(f, suffix)) filesFromFolders.push_back(f); } } catch (...){ qWarning() << "Could not read file/directory: " << file.c_str(); } } return filesFromFolders; } PluginLoader::PluginLoader(QObject *parent) { m_MetaData = nullptr; m_PluginLoader = new QPluginLoader(this); m_PluginListModel = new QStringListModel(); } PluginLoader::~PluginLoader() { delete m_PluginLoader; delete m_PluginListModel; } bool PluginLoader::loadPluginFromFilename(QString const& filename) { bool retval = false; if (m_PluginLoader->isLoaded()) { m_PluginLoader->unload(); } bool isLib = QLibrary::isLibrary(filename); if (isLib) { auto oldPath = PluginLoader::addDllPath(filename.toStdString()); m_PluginLoader->setFileName(filename); readMetaDataFromPlugin(); retval = m_PluginLoader->load(); QString s = m_PluginLoader->errorString(); std::string ss = s.toStdString(); addPluginnameToLists(getCurrentPluginName(), filename); if (!m_PluginLoader->isLoaded()) { qWarning() << ss.c_str(); retval = false; } PluginLoader::delDllPath(oldPath); } else { retval = false; } return retval; } void PluginLoader::addPluginnameToLists(QString mstring, QString filename) { if (!m_PluginList.contains(mstring)) m_PluginList.append(mstring); m_PluginListModel->setStringList(m_PluginList); m_PluginMap.insert(std::pair<QString, QString>(mstring, filename)); } bool PluginLoader::loadPluginFromName(QString name) { QString filename = m_PluginMap.find(name)->second; return loadPluginFromFilename(filename); } int PluginLoader::addToPluginList(QString filename, QString suffix) { if (!validSuffix(filename.toStdString(), suffix.toStdString())) return 1; bool isLib = QLibrary::isLibrary(filename); if (isLib) { QPluginLoader loader; loader.setFileName(filename); QJsonValue pluginMeda(loader.metaData().value("MetaData")); QJsonObject metaObj = pluginMeda.toObject(); QString mstring = metaObj.value("name").toString(); addPluginnameToLists(mstring, filename); } else { return 2; qWarning() << "Error reading plugin: " << filename; } return 0; } QStringListModel* PluginLoader::getPluginList() { return m_PluginListModel; } QObject* PluginLoader::getPluginInstance() { return (m_PluginLoader->instance()); } QJsonObject PluginLoader::getPluginMetaData() const { if (m_MetaData == nullptr) qFatal("(getPluginMetaData) No plugin loaded"); return *m_MetaData; } void PluginLoader::readMetaDataFromPlugin() { m_MetaData = std::make_shared<QJsonObject>(m_PluginLoader->metaData().value("MetaData").toObject()); } bool PluginLoader::getIsPluginLoaded() { return m_isPluginLoaded; } QString PluginLoader::getCurrentPluginName() { if (m_MetaData == nullptr) return "Error name"; return m_MetaData->value("name").toString(); } const std::map<QString, QString> &PluginLoader::getPluginMap() const { return m_PluginMap; }