mirror of
https://github.com/quizhizhe/LiteLoaderBDS-1.16.40.git
synced 2025-06-06 12:03:39 +00:00
392 lines
12 KiB
C++
392 lines
12 KiB
C++
#include <API/APIHelp.h>
|
|
#include "PluginManager.h"
|
|
#include <Global.hpp>
|
|
#include <Configs.h>
|
|
#include <filesystem>
|
|
#include <Engine/GlobalShareData.h>
|
|
#include <Engine/LocalShareData.h>
|
|
#include <Engine/MessageSystem.h>
|
|
#include <Engine/RemoteCall.h>
|
|
#include <Engine/TimeTaskSystem.h>
|
|
#include <Engine/EngineManager.h>
|
|
#include <Utils/STLHelper.h>
|
|
#include <LiteLoader/Main/PluginManager.h>
|
|
#include <Loader.h>
|
|
#include <ScheduleAPI.h>
|
|
#include <API/CommandAPI.h>
|
|
#include <API/EventAPI.h>
|
|
#include <Utils/Hash.h>
|
|
#ifdef LLSE_BACKEND_NODEJS
|
|
#pragma warning(disable : 4251)
|
|
#include <NodeJsHelper.h>
|
|
#endif
|
|
#define H(x) do_hash(x)
|
|
using namespace std;
|
|
|
|
extern void BindAPIs(ScriptEngine* engine);
|
|
|
|
// Helper
|
|
string RemoveRealAllExtension(string fileName) {
|
|
int pos = fileName.find(".");
|
|
if (pos == string::npos)
|
|
return fileName;
|
|
else
|
|
return fileName.substr(0, pos);
|
|
}
|
|
|
|
// Load plugin
|
|
// - This function must be called in correct backend
|
|
// - "filePath" can be a single-file plugin path, or a .llplugin compressed package path
|
|
// or a dir path which contains uncompressed plugin package
|
|
bool PluginManager::loadPlugin(const std::string& fileOrDirPath, bool isHotLoad, bool mustBeCurrentModule) {
|
|
if (fileOrDirPath == LLSE_DEBUG_ENGINE_NAME)
|
|
return true;
|
|
|
|
if (!filesystem::exists(str2wstr(fileOrDirPath))) {
|
|
logger.error("Plugin no found! Check the path you input again.");
|
|
return false;
|
|
}
|
|
|
|
// Get bacis information
|
|
bool isPluginPackage = filesystem::is_directory(fileOrDirPath);
|
|
string backendType = getPluginBackendType(fileOrDirPath);
|
|
if (backendType.empty())
|
|
{
|
|
logger.error(fileOrDirPath + " is not a valid plugin path!");
|
|
return false;
|
|
}
|
|
std::filesystem::path p(str2wstr(fileOrDirPath));
|
|
string pluginFileName = RemoveRealAllExtension(UTF82String(p.filename().stem().u8string()));
|
|
|
|
// Uncompress plugin package if needed
|
|
string realPath = fileOrDirPath;
|
|
if (backendType == "PluginPackage")
|
|
{
|
|
// Get plugin package
|
|
// Clean temp dir first
|
|
std::error_code ec;
|
|
filesystem::remove_all(LLSE_PLUGIN_PACKAGE_TEMP_DIR, ec);
|
|
|
|
// Uncompress package to temp dir
|
|
string uncompressToDir = LLSE_PLUGIN_PACKAGE_TEMP_DIR;
|
|
auto [exitCode, output] = UncompressFile(fileOrDirPath, uncompressToDir, LLSE_PLUGIN_PACKAGE_UNCOMPRESS_TIMEOUT);
|
|
if (exitCode != 0) {
|
|
logger.error("Fail to uncompress plugin package at " + fileOrDirPath + "!");
|
|
logger.debug(output);
|
|
return false;
|
|
}
|
|
|
|
// Re-get backendType
|
|
isPluginPackage = true;
|
|
realPath = uncompressToDir;
|
|
backendType = getPluginBackendType(realPath);
|
|
}
|
|
|
|
// Re-check backendType
|
|
if (backendType.empty() || backendType == "PluginPackage")
|
|
{
|
|
logger.error(pluginFileName + " is not a valid plugin!");
|
|
return false;
|
|
}
|
|
else if (backendType != LLSE_BACKEND_TYPE)
|
|
{
|
|
// Unmatched backend
|
|
// logger.error(pluginFileName + " is not a plugin of " + LLSE_BACKEND_TYPE + " engine!");
|
|
return false;
|
|
}
|
|
|
|
// Plugin package
|
|
if (isPluginPackage)
|
|
{
|
|
return loadPluginPackage(realPath, fileOrDirPath, isHotLoad);
|
|
}
|
|
|
|
// Single file plugin
|
|
// Check duplicated
|
|
if (PluginManager::getPlugin(pluginFileName)) {
|
|
// logger.error("This plugin has been loaded by LiteLoader. You cannot load it twice.");
|
|
return false;
|
|
}
|
|
|
|
ScriptEngine* engine = nullptr;
|
|
try {
|
|
auto scripts = ReadAllFile(realPath);
|
|
if (!scripts) {
|
|
throw std::runtime_error("Fail to open plugin file!");
|
|
}
|
|
|
|
// Create script engine
|
|
engine = EngineManager::newEngine();
|
|
EngineScope enter(engine);
|
|
|
|
// setData
|
|
ENGINE_OWN_DATA()->pluginName = pluginFileName;
|
|
ENGINE_OWN_DATA()->pluginFileOrDirPath = realPath;
|
|
ENGINE_OWN_DATA()->logger.title = pluginFileName;
|
|
|
|
// Bind APIs
|
|
try {
|
|
BindAPIs(engine);
|
|
} catch (const Exception& e) {
|
|
logger.error("Fail in Binding APIs!\n");
|
|
throw;
|
|
}
|
|
// Load depend libs
|
|
try {
|
|
for (auto& [path, content] : depends) {
|
|
engine->eval(content, path);
|
|
}
|
|
} catch (const Exception& e) {
|
|
logger.error("Fail in Loading Dependence Lib!\n");
|
|
throw;
|
|
}
|
|
|
|
// Load script
|
|
try {
|
|
engine->eval(*scripts, ENGINE_OWN_DATA()->pluginFileOrDirPath);
|
|
} catch (const Exception& e) {
|
|
logger.error("Fail in Loading Script Plugin!\n");
|
|
throw;
|
|
}
|
|
std::string const& pluginName = ENGINE_OWN_DATA()->pluginName;
|
|
ExitEngineScope exit;
|
|
|
|
// If plugin itself doesn't register, help it to do so
|
|
if (!PluginManager::getPlugin(pluginName))
|
|
PluginManager::registerPlugin(realPath, pluginName, pluginName, LL::Version(1, 0, 0), {});
|
|
|
|
// Call necessary events when at hot load
|
|
if (isHotLoad)
|
|
LLSECallEventsOnHotLoad(engine);
|
|
|
|
// Success
|
|
logger.info(tr("llse.loader.loadMain.loadedPlugin",
|
|
fmt::arg("type", backendType),
|
|
fmt::arg("name", pluginName)));
|
|
return true;
|
|
} catch (const Exception& e) {
|
|
logger.error("Fail to load " + realPath + "!");
|
|
if (engine) {
|
|
EngineScope enter(engine);
|
|
logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName);
|
|
PrintException(e);
|
|
ExitEngineScope exit;
|
|
|
|
LLSERemoveTimeTaskData(engine);
|
|
LLSERemoveAllEventListeners(engine);
|
|
LLSERemoveCmdRegister(engine);
|
|
LLSERemoveCmdCallback(engine);
|
|
LLSERemoveAllExportedFuncs(engine);
|
|
|
|
engine->getData().reset();
|
|
EngineManager::unRegisterEngine(engine);
|
|
}
|
|
if (engine) {
|
|
engine->destroy();
|
|
}
|
|
} catch (const std::exception& e) {
|
|
logger.error("Fail to load " + realPath + "!");
|
|
logger.error(TextEncoding::toUTF8(e.what()));
|
|
} catch (...) {
|
|
logger.error("Fail to load " + realPath + "!");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Load plugin package
|
|
// This function must be called in correct backend
|
|
bool PluginManager::loadPluginPackage(const std::string& dirPath, const std::string& packagePath, bool isHotLoad)
|
|
{
|
|
// "dirPath" is always public temp dir (LLSE_PLUGIN_PACKAGE_TEMP_DIR)
|
|
if (!filesystem::is_directory(dirPath))
|
|
return false;
|
|
bool result = false;
|
|
|
|
#ifdef LLSE_BACKEND_NODEJS
|
|
result = NodeJsHelper::loadNodeJsPlugin(dirPath, packagePath, isHotLoad);
|
|
#endif
|
|
|
|
if (result)
|
|
{
|
|
// OK now. Delete installed plugin package
|
|
std::error_code ec;
|
|
std::filesystem::remove(packagePath, ec);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Unload plugin
|
|
bool PluginManager::unloadPlugin(const std::string& name) {
|
|
if (name == LLSE_DEBUG_ENGINE_NAME)
|
|
return false;
|
|
|
|
auto engine = EngineManager::getEngine(name, true);
|
|
if (!engine)
|
|
return false;
|
|
string pluginName = ENGINE_GET_DATA(engine)->pluginName;
|
|
|
|
//NodeJs use his own setTimeout, so no need to remove
|
|
#ifndef LLSE_BACKEND_NODEJS
|
|
LLSERemoveTimeTaskData(engine);
|
|
#endif
|
|
|
|
LLSECallEventsOnHotUnload(engine);
|
|
LLSERemoveAllEventListeners(engine);
|
|
LLSERemoveCmdRegister(engine);
|
|
LLSERemoveCmdCallback(engine);
|
|
LLSERemoveAllExportedFuncs(engine);
|
|
|
|
EngineManager::unRegisterEngine(engine);
|
|
engine->getData().reset();
|
|
|
|
PluginManager::unRegisterPlugin(name);
|
|
Schedule::nextTick([engine]() {
|
|
#ifdef LLSE_BACKEND_NODEJS
|
|
NodeJsHelper::stopEngine(engine);
|
|
#else
|
|
engine->destroy();
|
|
#endif
|
|
});
|
|
|
|
logger.info(pluginName + " unloaded.");
|
|
return true;
|
|
}
|
|
|
|
// Reload plugin
|
|
bool PluginManager::reloadPlugin(const std::string& name) {
|
|
if (name == LLSE_DEBUG_ENGINE_NAME)
|
|
return true;
|
|
|
|
auto plugin = PluginManager::getPlugin(name);
|
|
if (!plugin)
|
|
return false;
|
|
|
|
string filePath = plugin->filePath;
|
|
if (!PluginManager::unloadPlugin(name))
|
|
return false;
|
|
return PluginManager::loadPlugin(filePath, true, true);
|
|
}
|
|
|
|
// Reload all plugins
|
|
bool PluginManager::reloadAllPlugins() {
|
|
auto pluginsList = PluginManager::getLocalPlugins();
|
|
for (auto& plugin : pluginsList)
|
|
reloadPlugin(plugin.second->name);
|
|
return true;
|
|
}
|
|
|
|
LL::Plugin* PluginManager::getPlugin(std::string name) {
|
|
return LL::PluginManager::getPlugin(name, true);
|
|
}
|
|
|
|
// Get all plugins of current language
|
|
std::unordered_map<std::string, LL::Plugin*> PluginManager::getLocalPlugins() {
|
|
std::unordered_map<std::string, LL::Plugin*> res;
|
|
|
|
auto engines = EngineManager::getLocalEngines();
|
|
for (auto& engine : engines) {
|
|
string name = ENGINE_GET_DATA(engine)->pluginName;
|
|
if (name != LLSE_DEBUG_ENGINE_NAME) {
|
|
LL::Plugin* plugin = PluginManager::getPlugin(name);
|
|
if (plugin)
|
|
res[plugin->name] = plugin;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::unordered_map<std::string, LL::Plugin*> PluginManager::getAllScriptPlugins() {
|
|
auto res = getAllPlugins();
|
|
erase_if(res, [](auto& item) {
|
|
return item.second->type != LL::Plugin::PluginType::ScriptPlugin;
|
|
});
|
|
return res;
|
|
}
|
|
|
|
// Get all plugins
|
|
std::unordered_map<std::string, LL::Plugin*> PluginManager::getAllPlugins() {
|
|
return LL::PluginManager::getAllPlugins();
|
|
}
|
|
|
|
bool PluginManager::registerPlugin(std::string filePath, std::string name, std::string desc,
|
|
LL::Version version, std::map<std::string, std::string> others) {
|
|
others["PluginType"] = "Script Plugin";
|
|
others["PluginFilePath"] = filePath;
|
|
return LL::PluginManager::registerPlugin(NULL, name, desc, version, others);
|
|
}
|
|
|
|
bool PluginManager::unRegisterPlugin(std::string name) {
|
|
return LL::PluginManager::unRegisterPlugin(name);
|
|
}
|
|
|
|
// Get plugin backend type from its file path (single file plugin)
|
|
// or its unpressed dir path (plugin package)
|
|
std::string PluginManager::getPluginBackendType(const std::string& path)
|
|
{
|
|
filesystem::path filePath(str2wstr(path));
|
|
if (!filesystem::exists(filePath))
|
|
return "";
|
|
|
|
if (filesystem::is_directory(filePath))
|
|
{
|
|
// Uncompressed plugin package
|
|
auto identifiers = LLSE_VALID_PLUGIN_PACKAGE_IDENTIFIER;
|
|
vector<string> filesExts = {};
|
|
for (int i = 0; i < identifiers.size(); ++i)
|
|
if (!identifiers.empty())
|
|
{
|
|
string id = identifiers[i];
|
|
if (id.empty())
|
|
continue;
|
|
|
|
if (id.find("*") != std::string::npos)
|
|
{
|
|
// match identifier like "*.py"
|
|
if (filesExts.empty())
|
|
{
|
|
// build filesExts list
|
|
std::filesystem::directory_iterator files(filePath);
|
|
for (auto& i : files) {
|
|
if (i.is_regular_file())
|
|
filesExts.emplace_back(UTF82String(i.path().extension().u8string()));
|
|
}
|
|
}
|
|
string compareExt = id.substr(id.find_last_of("."));
|
|
if (std::find(filesExts.begin(), filesExts.end(), compareExt) != filesExts.end())
|
|
{
|
|
// match
|
|
return LLSE_VALID_BACKENDS[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// match identifier like "package.json"
|
|
if (filesystem::exists(filePath / id))
|
|
{
|
|
// match
|
|
return LLSE_VALID_BACKENDS[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Common plugin file
|
|
string ext = UTF82String(filePath.extension().u8string());
|
|
if (ext == LLSE_PLUGIN_PACKAGE_EXTENSION)
|
|
{
|
|
// Never consider .llplugin
|
|
// Just uncompress it and then come to check
|
|
return "PluginPackage";
|
|
}
|
|
auto validExts = LLSE_VALID_PLUGIN_EXTENSIONS;
|
|
for (int i = 0; i < validExts.size(); ++i)
|
|
if (!validExts[i].empty() && validExts[i] == ext)
|
|
{
|
|
// match
|
|
return LLSE_VALID_BACKENDS[i];
|
|
}
|
|
}
|
|
// none backend matched
|
|
return "";
|
|
} |