684 lines
15 KiB
C++
684 lines
15 KiB
C++
#include "skse64/PluginManager.h"
|
|
#include "common/IDirectoryIterator.h"
|
|
#include "skse64/GameAPI.h"
|
|
#include "skse64_common/Utilities.h"
|
|
#include "skse64/Serialization.h"
|
|
#include "skse64_common/skse_version.h"
|
|
#include "skse64/PapyrusEvents.h"
|
|
#include "skse64_common/BranchTrampoline.h"
|
|
|
|
PluginManager g_pluginManager;
|
|
|
|
PluginManager::LoadedPlugin * PluginManager::s_currentLoadingPlugin = NULL;
|
|
PluginHandle PluginManager::s_currentPluginHandle = 0;
|
|
UInt32 s_trampolineLog = 1;
|
|
|
|
extern EventDispatcher<SKSEModCallbackEvent> g_modCallbackEventDispatcher;
|
|
extern EventDispatcher<SKSECameraEvent> g_cameraEventDispatcher;
|
|
extern EventDispatcher<SKSECrosshairRefEvent> g_crosshairRefEventDispatcher;
|
|
extern EventDispatcher<SKSEActionEvent> g_actionEventDispatcher;
|
|
|
|
BranchTrampolineManager g_branchTrampolineManager(g_branchTrampoline);
|
|
BranchTrampolineManager g_localTrampolineManager(g_localTrampoline);
|
|
|
|
static const SKSEInterface g_SKSEInterface =
|
|
{
|
|
PACKED_SKSE_VERSION,
|
|
|
|
#ifdef RUNTIME
|
|
RUNTIME_VERSION,
|
|
0,
|
|
0,
|
|
#else
|
|
0,
|
|
EDITOR_VERSION,
|
|
1,
|
|
#endif
|
|
|
|
PluginManager::QueryInterface,
|
|
PluginManager::GetPluginHandle,
|
|
PluginManager::GetReleaseIndex,
|
|
PluginManager::GetPluginInfo
|
|
};
|
|
|
|
#ifdef RUNTIME
|
|
|
|
#include "Hooks_Scaleform.h"
|
|
|
|
static const SKSEScaleformInterface g_SKSEScaleformInterface =
|
|
{
|
|
SKSEScaleformInterface::kInterfaceVersion,
|
|
|
|
RegisterScaleformPlugin,
|
|
RegisterScaleformInventory
|
|
};
|
|
|
|
#include "Hooks_Threads.h"
|
|
|
|
static const SKSETaskInterface g_SKSETaskInterface =
|
|
{
|
|
SKSETaskInterface::kInterfaceVersion,
|
|
|
|
TaskInterface::AddTask,
|
|
TaskInterface::AddUITask
|
|
};
|
|
|
|
#include "skse64/Hooks_Papyrus.h"
|
|
#include "skse64/PapyrusVM.h"
|
|
|
|
static const SKSEPapyrusInterface g_SKSEPapyrusInterface =
|
|
{
|
|
SKSEPapyrusInterface::kInterfaceVersion,
|
|
RegisterPapyrusPlugin
|
|
};
|
|
|
|
static SKSEMessagingInterface g_SKSEMessagingInterface =
|
|
{
|
|
SKSEMessagingInterface::kInterfaceVersion,
|
|
PluginManager::RegisterListener,
|
|
PluginManager::Dispatch_Message,
|
|
PluginManager::GetEventDispatcher,
|
|
};
|
|
|
|
#include "skse64/PapyrusDelayFunctors.h"
|
|
#include "skse64/PapyrusObjects.h"
|
|
|
|
static const SKSEObjectInterface g_SKSEObjectInterface =
|
|
{
|
|
SKSEObjectInterface::kInterfaceVersion,
|
|
SKSEDelayFunctorManagerInstance,
|
|
SKSEObjectRegistryInstance,
|
|
SKSEObjectStorageInstance
|
|
};
|
|
|
|
static const SKSETrampolineInterface g_SKSETrampolineInterface =
|
|
{
|
|
SKSETrampolineInterface::kInterfaceVersion,
|
|
AllocateFromSKSEBranchPool,
|
|
AllocateFromSKSELocalPool
|
|
};
|
|
#endif
|
|
|
|
PluginManager::PluginManager()
|
|
{
|
|
//
|
|
}
|
|
|
|
PluginManager::~PluginManager()
|
|
{
|
|
DeInit();
|
|
}
|
|
|
|
bool PluginManager::Init(void)
|
|
{
|
|
bool result = false;
|
|
|
|
if(FindPluginDirectory())
|
|
{
|
|
_MESSAGE("plugin directory = %s", m_pluginDirectory.c_str());
|
|
|
|
__try
|
|
{
|
|
InstallPlugins();
|
|
|
|
result = true;
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// something very bad happened
|
|
_ERROR("exception occurred while loading plugins");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void PluginManager::DeInit(void)
|
|
{
|
|
for(LoadedPluginList::iterator iter = m_plugins.begin(); iter != m_plugins.end(); ++iter)
|
|
{
|
|
LoadedPlugin * plugin = &(*iter);
|
|
|
|
if(plugin->handle)
|
|
{
|
|
FreeLibrary(plugin->handle);
|
|
}
|
|
}
|
|
|
|
m_plugins.clear();
|
|
}
|
|
|
|
UInt32 PluginManager::GetNumPlugins(void)
|
|
{
|
|
UInt32 numPlugins = m_plugins.size();
|
|
|
|
// is one currently loading?
|
|
if(s_currentLoadingPlugin) numPlugins++;
|
|
|
|
return numPlugins;
|
|
}
|
|
|
|
PluginInfo * PluginManager::GetInfoByName(const char * name)
|
|
{
|
|
for(LoadedPluginList::iterator iter = m_plugins.begin(); iter != m_plugins.end(); ++iter)
|
|
{
|
|
LoadedPlugin * plugin = &(*iter);
|
|
|
|
if(plugin->info.name && !_stricmp(name, plugin->info.name))
|
|
return &plugin->info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void * PluginManager::QueryInterface(UInt32 id)
|
|
{
|
|
void * result = NULL;
|
|
|
|
#ifdef RUNTIME
|
|
switch(id)
|
|
{
|
|
case kInterface_Papyrus:
|
|
result = (void *)&g_SKSEPapyrusInterface;
|
|
break;
|
|
case kInterface_Serialization:
|
|
result = (void *)&g_SKSESerializationInterface;
|
|
break;
|
|
case kInterface_Messaging:
|
|
result = (void *)&g_SKSEMessagingInterface;
|
|
break;
|
|
case kInterface_Scaleform:
|
|
result = (void *)&g_SKSEScaleformInterface;
|
|
break;
|
|
case kInterface_Task:
|
|
result = (void *)&g_SKSETaskInterface;
|
|
break;
|
|
case kInterface_Object:
|
|
result = (void *)&g_SKSEObjectInterface;
|
|
break;
|
|
case kInterface_Trampoline:
|
|
result = (void *)&g_SKSETrampolineInterface;
|
|
break;
|
|
|
|
default:
|
|
_WARNING("unknown QueryInterface %08X", id);
|
|
break;
|
|
}
|
|
#else
|
|
_WARNING("unknown QueryInterface %08X", id);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
PluginHandle PluginManager::GetPluginHandle(void)
|
|
{
|
|
ASSERT_STR(s_currentPluginHandle, "A plugin has called SKSEInterface::GetPluginHandle outside of its Query/Load handlers");
|
|
|
|
return s_currentPluginHandle;
|
|
}
|
|
|
|
UInt32 PluginManager::GetReleaseIndex( void )
|
|
{
|
|
return SKSE_VERSION_RELEASEIDX;
|
|
}
|
|
|
|
const PluginInfo* PluginManager::GetPluginInfo(const char* name)
|
|
{
|
|
return g_pluginManager.GetInfoByName(name);
|
|
}
|
|
|
|
bool PluginManager::FindPluginDirectory(void)
|
|
{
|
|
bool result = false;
|
|
|
|
// find the path <runtime directory>/data/skse/
|
|
std::string runtimeDirectory = GetRuntimeDirectory();
|
|
|
|
if(!runtimeDirectory.empty())
|
|
{
|
|
m_pluginDirectory = runtimeDirectory + "Data\\SKSE\\Plugins\\";
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void PluginManager::InstallPlugins(void)
|
|
{
|
|
// avoid realloc
|
|
m_plugins.reserve(5);
|
|
|
|
for(IDirectoryIterator iter(m_pluginDirectory.c_str(), "*.dll"); !iter.Done(); iter.Next())
|
|
{
|
|
std::string pluginPath = iter.GetFullPath();
|
|
|
|
_MESSAGE("checking plugin %s", pluginPath.c_str());
|
|
|
|
LoadedPlugin plugin;
|
|
memset(&plugin, 0, sizeof(plugin));
|
|
|
|
s_currentLoadingPlugin = &plugin;
|
|
s_currentPluginHandle = m_plugins.size() + 1; // +1 because 0 is reserved for internal use
|
|
|
|
plugin.handle = (HMODULE)LoadLibrary(pluginPath.c_str());
|
|
if(plugin.handle)
|
|
{
|
|
bool success = false;
|
|
|
|
plugin.query = (_SKSEPlugin_Query)GetProcAddress(plugin.handle, "SKSEPlugin_Query");
|
|
plugin.load = (_SKSEPlugin_Load)GetProcAddress(plugin.handle, "SKSEPlugin_Load");
|
|
|
|
if(plugin.query && plugin.load)
|
|
{
|
|
const char * loadStatus = NULL;
|
|
|
|
loadStatus = SafeCallQueryPlugin(&plugin, &g_SKSEInterface);
|
|
|
|
if(!loadStatus)
|
|
{
|
|
loadStatus = CheckPluginCompatibility(&plugin);
|
|
|
|
if(!loadStatus)
|
|
{
|
|
loadStatus = SafeCallLoadPlugin(&plugin, &g_SKSEInterface);
|
|
|
|
if(!loadStatus)
|
|
{
|
|
loadStatus = "loaded correctly";
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
loadStatus = "reported as incompatible during query";
|
|
}
|
|
|
|
ASSERT(loadStatus);
|
|
|
|
_MESSAGE("plugin %s (%08X %s %08X) %s (handle %d)",
|
|
pluginPath.c_str(),
|
|
plugin.info.infoVersion,
|
|
plugin.info.name ? plugin.info.name : "<NULL>",
|
|
plugin.info.version,
|
|
loadStatus,
|
|
s_currentPluginHandle);
|
|
}
|
|
else
|
|
{
|
|
_MESSAGE("plugin %s does not appear to be an SKSE plugin", pluginPath.c_str());
|
|
}
|
|
|
|
if(success)
|
|
{
|
|
// succeeded, add it to the list
|
|
m_plugins.push_back(plugin);
|
|
}
|
|
else
|
|
{
|
|
// failed, unload the library
|
|
FreeLibrary(plugin.handle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_ERROR("couldn't load plugin %s (Error %d)", pluginPath.c_str(), GetLastError());
|
|
}
|
|
}
|
|
|
|
s_currentLoadingPlugin = NULL;
|
|
s_currentPluginHandle = 0;
|
|
|
|
// alert any listeners that plugin load has finished
|
|
Dispatch_Message(0, SKSEMessagingInterface::kMessage_PostLoad, NULL, 0, NULL);
|
|
// second post-load dispatch
|
|
Dispatch_Message(0, SKSEMessagingInterface::kMessage_PostPostLoad, NULL, 0, NULL);
|
|
}
|
|
|
|
// SEH-wrapped calls to plugin API functions to avoid bugs from bringing down the core
|
|
const char * PluginManager::SafeCallQueryPlugin(LoadedPlugin * plugin, const SKSEInterface * skse)
|
|
{
|
|
__try
|
|
{
|
|
if(!plugin->query(skse, &plugin->info))
|
|
{
|
|
return "reported as incompatible during query";
|
|
}
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// something very bad happened
|
|
return "disabled, fatal error occurred while querying plugin";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char * PluginManager::SafeCallLoadPlugin(LoadedPlugin * plugin, const SKSEInterface * skse)
|
|
{
|
|
__try
|
|
{
|
|
if(!plugin->load(skse))
|
|
{
|
|
return "reported as incompatible during load";
|
|
}
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// something very bad happened
|
|
return "disabled, fatal error occurred while loading plugin";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
enum
|
|
{
|
|
kCompat_BlockFromRuntime = 1 << 0,
|
|
kCompat_BlockFromEditor = 1 << 1,
|
|
};
|
|
|
|
struct MinVersionEntry
|
|
{
|
|
const char * name;
|
|
UInt32 minVersion;
|
|
const char * reason;
|
|
UInt32 compatFlags;
|
|
};
|
|
|
|
static const MinVersionEntry kMinVersionList[] =
|
|
{
|
|
// returns true for multiple versions of the runtime
|
|
{ "papyrusutil plugin", 2, "broken version check, will crash the game", kCompat_BlockFromRuntime },
|
|
|
|
{ NULL, 0, NULL }
|
|
};
|
|
|
|
// see if we have a plugin that we know causes problems
|
|
const char * PluginManager::CheckPluginCompatibility(LoadedPlugin * plugin)
|
|
{
|
|
__try
|
|
{
|
|
// stupid plugin check
|
|
if(!plugin->info.name)
|
|
{
|
|
return "disabled, no name specified";
|
|
}
|
|
|
|
// check for 'known bad' versions of plugins
|
|
for(const MinVersionEntry * iter = kMinVersionList; iter->name; ++iter)
|
|
{
|
|
if(!strcmp(iter->name, plugin->info.name))
|
|
{
|
|
if(plugin->info.version < iter->minVersion)
|
|
{
|
|
#ifdef RUNTIME
|
|
if(iter->compatFlags & kCompat_BlockFromRuntime)
|
|
{
|
|
return iter->reason;
|
|
}
|
|
#endif
|
|
|
|
#ifdef EDITOR
|
|
if(iter->compatFlags & kCompat_BlockFromEditor)
|
|
{
|
|
return iter->reason;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// paranoia
|
|
return "disabled, fatal error occurred while checking plugin compatibility";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void * PluginManager::GetEventDispatcher(UInt32 dispatcherId)
|
|
{
|
|
void * result = NULL;
|
|
|
|
#ifdef RUNTIME
|
|
switch(dispatcherId)
|
|
{
|
|
case SKSEMessagingInterface::kDispatcher_ModEvent:
|
|
result = (void *)&g_modCallbackEventDispatcher;
|
|
break;
|
|
case SKSEMessagingInterface::kDispatcher_CameraEvent:
|
|
result = (void *)&g_cameraEventDispatcher;
|
|
break;
|
|
case SKSEMessagingInterface::kDispatcher_CrosshairEvent:
|
|
result = (void *)&g_crosshairRefEventDispatcher;
|
|
break;
|
|
case SKSEMessagingInterface::kDispatcher_ActionEvent:
|
|
result = (void *)&g_actionEventDispatcher;
|
|
break;
|
|
case SKSEMessagingInterface::kDispatcher_NiNodeUpdateEvent:
|
|
result = (void *)&g_ninodeUpdateEventDispatcher;
|
|
break;
|
|
|
|
default:
|
|
_WARNING("unknown EventDispatcher %08X", dispatcherId);
|
|
break;
|
|
}
|
|
#else
|
|
_WARNING("unknown EventDispatcher %08X", id);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// Plugin communication interface
|
|
struct PluginListener {
|
|
PluginHandle listener;
|
|
SKSEMessagingInterface::EventCallback handleMessage;
|
|
};
|
|
|
|
typedef std::vector<std::vector<PluginListener> > PluginListeners;
|
|
static PluginListeners s_pluginListeners;
|
|
|
|
bool PluginManager::RegisterListener(PluginHandle listener, const char* sender, SKSEMessagingInterface::EventCallback handler)
|
|
{
|
|
// because this can be called while plugins are loading, gotta make sure number of plugins hasn't increased
|
|
UInt32 numPlugins = g_pluginManager.GetNumPlugins() + 1;
|
|
if (s_pluginListeners.size() < numPlugins)
|
|
{
|
|
s_pluginListeners.resize(numPlugins + 5); // add some extra room to avoid unnecessary re-alloc
|
|
}
|
|
|
|
_MESSAGE("registering plugin listener for %s at %u of %u", sender, listener, numPlugins);
|
|
|
|
// handle > num plugins = invalid
|
|
if (listener > g_pluginManager.GetNumPlugins() || !handler)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sender)
|
|
{
|
|
// is target loaded?
|
|
PluginHandle target = g_pluginManager.LookupHandleFromName(sender);
|
|
if (target == kPluginHandle_Invalid)
|
|
{
|
|
return false;
|
|
}
|
|
// is listener already registered?
|
|
for (std::vector<PluginListener>::iterator iter = s_pluginListeners[target].begin(); iter != s_pluginListeners[target].end(); ++iter)
|
|
{
|
|
if (iter->listener == listener)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// register new listener
|
|
PluginListener newListener;
|
|
newListener.handleMessage = handler;
|
|
newListener.listener = listener;
|
|
|
|
s_pluginListeners[target].push_back(newListener);
|
|
}
|
|
else
|
|
{
|
|
// register listener to every loaded plugin
|
|
UInt32 idx = 0;
|
|
for(PluginListeners::iterator iter = s_pluginListeners.begin(); iter != s_pluginListeners.end(); ++iter)
|
|
{
|
|
// don't add the listener to its own list
|
|
if (idx && idx != listener)
|
|
{
|
|
bool skipCurrentList = false;
|
|
for (std::vector<PluginListener>::iterator iterEx = iter->begin(); iterEx != iter->end(); ++iterEx)
|
|
{
|
|
// already registered with this plugin, skip it
|
|
if (iterEx->listener == listener)
|
|
{
|
|
skipCurrentList = true;
|
|
break;
|
|
}
|
|
}
|
|
if (skipCurrentList)
|
|
{
|
|
continue;
|
|
}
|
|
PluginListener newListener;
|
|
newListener.handleMessage = handler;
|
|
newListener.listener = listener;
|
|
|
|
iter->push_back(newListener);
|
|
}
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PluginManager::Dispatch_Message(PluginHandle sender, UInt32 messageType, void * data, UInt32 dataLen, const char* receiver)
|
|
{
|
|
_MESSAGE("dispatch message (%d) to plugin listeners", messageType);
|
|
UInt32 numRespondents = 0;
|
|
PluginHandle target = kPluginHandle_Invalid;
|
|
|
|
if (!s_pluginListeners.size()) // no listeners yet registered
|
|
{
|
|
_MESSAGE("no listeners registered");
|
|
return false;
|
|
}
|
|
else if (sender >= s_pluginListeners.size())
|
|
{
|
|
_MESSAGE("sender is not in the list");
|
|
return false;
|
|
}
|
|
|
|
if (receiver)
|
|
{
|
|
target = g_pluginManager.LookupHandleFromName(receiver);
|
|
if (target == kPluginHandle_Invalid)
|
|
return false;
|
|
}
|
|
|
|
const char* senderName = g_pluginManager.GetPluginNameFromHandle(sender);
|
|
if (!senderName)
|
|
return false;
|
|
for (std::vector<PluginListener>::iterator iter = s_pluginListeners[sender].begin(); iter != s_pluginListeners[sender].end(); ++iter)
|
|
{
|
|
SKSEMessagingInterface::Message msg;
|
|
msg.data = data;
|
|
msg.type = messageType;
|
|
msg.sender = senderName;
|
|
msg.dataLen = dataLen;
|
|
|
|
if (target != kPluginHandle_Invalid) // sending message to specific plugin
|
|
{
|
|
if (iter->listener == target)
|
|
{
|
|
iter->handleMessage(&msg);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DMESSAGE("sending message type %u to plugin %u", messageType, iter->listener);
|
|
iter->handleMessage(&msg);
|
|
numRespondents++;
|
|
}
|
|
}
|
|
_DMESSAGE("dispatched message.");
|
|
return numRespondents ? true : false;
|
|
}
|
|
|
|
const char * PluginManager::GetPluginNameFromHandle(PluginHandle handle)
|
|
{
|
|
if (handle > 0 && handle <= m_plugins.size())
|
|
return (m_plugins[handle - 1].info.name);
|
|
else if (handle == 0)
|
|
return "SKSE";
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PluginHandle PluginManager::LookupHandleFromName(const char* pluginName)
|
|
{
|
|
if (!_stricmp("SKSE", pluginName))
|
|
return 0;
|
|
|
|
UInt32 idx = 1;
|
|
for(LoadedPluginList::iterator iter = m_plugins.begin(); iter != m_plugins.end(); ++iter)
|
|
{
|
|
LoadedPlugin * plugin = &(*iter);
|
|
if(!_stricmp(plugin->info.name, pluginName))
|
|
{
|
|
return idx;
|
|
}
|
|
idx++;
|
|
}
|
|
return kPluginHandle_Invalid;
|
|
}
|
|
|
|
inline void * BranchTrampolineManager::Allocate(PluginHandle plugin, size_t size)
|
|
{
|
|
auto mem = m_trampoline.Allocate(size);
|
|
if (mem) {
|
|
std::lock_guard<decltype(m_lock)> locker(m_lock);
|
|
|
|
auto findIt = m_stats.find(plugin);
|
|
if (findIt != m_stats.end()) {
|
|
findIt->second += size;
|
|
}
|
|
else {
|
|
auto insIt = m_stats.insert(std::make_pair(plugin, size));
|
|
ASSERT(insIt.second); // insertion failed
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(false); // alloc failed
|
|
}
|
|
return mem;
|
|
}
|
|
|
|
|
|
void * AllocateFromSKSEBranchPool(PluginHandle plugin, size_t size)
|
|
{
|
|
if (s_trampolineLog) {
|
|
_DMESSAGE("plugin %d allocated %lld bytes from branch pool", plugin, size);
|
|
}
|
|
return g_branchTrampolineManager.Allocate(plugin, size);
|
|
}
|
|
|
|
void * AllocateFromSKSELocalPool(PluginHandle plugin, size_t size)
|
|
{
|
|
if (s_trampolineLog) {
|
|
_DMESSAGE("plugin %d allocated %lld bytes from local pool", plugin, size);
|
|
}
|
|
return g_localTrampolineManager.Allocate(plugin, size);
|
|
}
|