Enderal SE
https://mod.pub/enderal-se/38-enderal-se
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
968 lines
22 KiB
968 lines
22 KiB
#include "skse64/PluginManager.h"
|
|
#include "common/IDirectoryIterator.h"
|
|
#include "common/IFileStream.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"
|
|
#include "resource.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();
|
|
}
|
|
|
|
PluginManager::LoadedPlugin::LoadedPlugin()
|
|
:handle(0)
|
|
,load(nullptr)
|
|
{
|
|
memset(&info, 0, sizeof(info));
|
|
memset(&version, 0, sizeof(version));
|
|
}
|
|
|
|
bool PluginManager::Init(void)
|
|
{
|
|
bool result = false;
|
|
|
|
if(FindPluginDirectory())
|
|
{
|
|
_MESSAGE("plugin directory = %s", m_pluginDirectory.c_str());
|
|
|
|
// avoid realloc
|
|
m_plugins.reserve(5);
|
|
|
|
__try
|
|
{
|
|
ScanPlugins();
|
|
InstallPlugins();
|
|
|
|
result = true;
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// something very bad happened
|
|
_ERROR("exception occurred while loading plugins");
|
|
}
|
|
}
|
|
|
|
ReportPluginErrors();
|
|
|
|
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::ScanPlugins(void)
|
|
{
|
|
_MESSAGE("scanning plugin directory %s", m_pluginDirectory.c_str());
|
|
|
|
UInt32 handleIdx = 1; // start at 1, 0 is reserved for internal use
|
|
|
|
for(IDirectoryIterator iter(m_pluginDirectory.c_str(), "*.dll"); !iter.Done(); iter.Next())
|
|
{
|
|
std::string pluginPath = iter.GetFullPath();
|
|
|
|
LoadedPlugin plugin;
|
|
plugin.dllName = iter.Get()->cFileName;
|
|
|
|
_MESSAGE("checking plugin %s", plugin.dllName.c_str());
|
|
|
|
HMODULE resourceHandle = (HMODULE)LoadLibraryEx(pluginPath.c_str(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
|
|
if(resourceHandle)
|
|
{
|
|
if(Is64BitDLL(resourceHandle))
|
|
{
|
|
auto * version = (const SKSEPluginVersionData *)GetResourceLibraryProcAddress(resourceHandle, "SKSEPlugin_Version");
|
|
if(version)
|
|
{
|
|
plugin.version = *version;
|
|
Sanitize(&plugin.version);
|
|
|
|
auto * loadStatus = CheckPluginCompatibility(plugin.version);
|
|
if(!loadStatus)
|
|
{
|
|
// compatible, add to list
|
|
|
|
plugin.internalHandle = handleIdx;
|
|
handleIdx++;
|
|
|
|
m_plugins.push_back(plugin);
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, loadStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, "no version data", 0, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, "LE plugin cannot be used with SE");
|
|
}
|
|
|
|
FreeLibrary(resourceHandle);
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, "couldn't load plugin", GetLastError());
|
|
}
|
|
}
|
|
}
|
|
|
|
const char * PluginManager::CheckAddressLibrary(void)
|
|
{
|
|
static bool s_checked = false;
|
|
static const char * s_status = nullptr;
|
|
|
|
if(s_checked)
|
|
{
|
|
return s_status;
|
|
}
|
|
|
|
char fileName[256];
|
|
_snprintf_s(fileName, 256, "Data\\SKSE\\Plugins\\versionlib-%d-%d-%d-%d.bin",
|
|
GET_EXE_VERSION_MAJOR(RUNTIME_VERSION),
|
|
GET_EXE_VERSION_MINOR(RUNTIME_VERSION),
|
|
GET_EXE_VERSION_BUILD(RUNTIME_VERSION),
|
|
0);
|
|
|
|
IFileStream versionLib;
|
|
if(!versionLib.Open(fileName))
|
|
{
|
|
m_oldAddressLibrary = true;
|
|
s_status = "disabled, address library needs to be updated";
|
|
}
|
|
|
|
s_checked = true;
|
|
|
|
return s_status;
|
|
}
|
|
|
|
void PluginManager::InstallPlugins(void)
|
|
{
|
|
for(size_t i = 0; i < m_plugins.size(); i++)
|
|
{
|
|
auto & plugin = m_plugins[i];
|
|
|
|
_MESSAGE("loading plugin \"%s\"", plugin.version.name);
|
|
|
|
s_currentLoadingPlugin = &plugin;
|
|
s_currentPluginHandle = plugin.internalHandle;
|
|
|
|
std::string pluginPath = m_pluginDirectory + plugin.dllName;
|
|
|
|
plugin.handle = (HMODULE)LoadLibrary(pluginPath.c_str());
|
|
if(plugin.handle)
|
|
{
|
|
bool success = false;
|
|
|
|
plugin.load = (_SKSEPlugin_Load)GetProcAddress(plugin.handle, "SKSEPlugin_Load");
|
|
if(plugin.load)
|
|
{
|
|
const char * loadStatus = NULL;
|
|
|
|
loadStatus = SafeCallLoadPlugin(&plugin, &g_SKSEInterface);
|
|
|
|
if(!loadStatus)
|
|
{
|
|
success = true;
|
|
loadStatus = "loaded correctly";
|
|
}
|
|
|
|
ASSERT(loadStatus);
|
|
|
|
if(success)
|
|
{
|
|
_MESSAGE("plugin %s (%08X %s %08X) %s (handle %d)",
|
|
plugin.dllName.c_str(),
|
|
plugin.version.dataVersion,
|
|
plugin.version.name,
|
|
plugin.version.pluginVersion,
|
|
loadStatus,
|
|
s_currentPluginHandle);
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, loadStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, "does not appear to be an SKSE plugin");
|
|
}
|
|
|
|
if(!success)
|
|
{
|
|
// failed, unload the library
|
|
FreeLibrary(plugin.handle);
|
|
|
|
// and remove from plugins list
|
|
m_plugins.erase(m_plugins.begin() + i);
|
|
|
|
// fix iterator
|
|
i--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPluginLoadError(plugin, "couldn't load plugin", GetLastError());
|
|
}
|
|
}
|
|
|
|
s_currentLoadingPlugin = NULL;
|
|
s_currentPluginHandle = 0;
|
|
|
|
// make fake PluginInfo structs after m_plugins is locked
|
|
for(auto & plugin : m_plugins)
|
|
{
|
|
plugin.info.infoVersion = PluginInfo::kInfoVersion;
|
|
plugin.info.name = plugin.version.name;
|
|
plugin.info.version = plugin.version.pluginVersion;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void PluginManager::Sanitize(SKSEPluginVersionData * version)
|
|
{
|
|
version->name[sizeof(version->name) - 1] = 0;
|
|
version->author[sizeof(version->author) - 1] = 0;
|
|
version->supportEmail[sizeof(version->supportEmail) - 1] = 0;
|
|
}
|
|
|
|
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[] =
|
|
{
|
|
{ NULL, 0, NULL }
|
|
};
|
|
|
|
const char * PluginManager::CheckPluginCompatibility(const SKSEPluginVersionData & version)
|
|
{
|
|
__try
|
|
{
|
|
// basic validity
|
|
if(version.dataVersion != SKSEPluginVersionData::kVersion)
|
|
{
|
|
return "disabled, bad version data";
|
|
}
|
|
|
|
if(!version.name[0])
|
|
{
|
|
return "disabled, no name specified";
|
|
}
|
|
|
|
// check for 'known bad' versions of plugins
|
|
for(const MinVersionEntry * iter = kMinVersionList; iter->name; ++iter)
|
|
{
|
|
if(!strcmp(iter->name, version.name))
|
|
{
|
|
if(version.pluginVersion < 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;
|
|
}
|
|
}
|
|
|
|
// version compatibility
|
|
const UInt32 kIndependentMask =
|
|
SKSEPluginVersionData::kVersionIndependent_AddressLibraryPostAE |
|
|
SKSEPluginVersionData::kVersionIndependent_Signatures;
|
|
|
|
if(version.versionIndependence & ~kIndependentMask)
|
|
{
|
|
return "disabled, unsupported version independence method";
|
|
}
|
|
|
|
if(version.versionIndependence & SKSEPluginVersionData::kVersionIndependent_AddressLibraryPostAE)
|
|
{
|
|
const char * result = CheckAddressLibrary();
|
|
if(result) return result;
|
|
}
|
|
else if(!version.versionIndependence)
|
|
{
|
|
bool found = false;
|
|
|
|
for(UInt32 i = 0; i < _countof(version.compatibleVersions); i++)
|
|
{
|
|
UInt32 compatibleVersion = version.compatibleVersions[i];
|
|
|
|
if(compatibleVersion == RUNTIME_VERSION)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
else if(!compatibleVersion)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
return "disabled, incompatible with current runtime version";
|
|
}
|
|
}
|
|
|
|
// SE version compatibility
|
|
if(version.seVersionRequired > PACKED_SKSE_VERSION)
|
|
{
|
|
return "disabled, requires newer script extender";
|
|
}
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// paranoia
|
|
return "disabled, fatal error occurred while checking plugin compatibility";
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void PluginManager::LogPluginLoadError(const LoadedPlugin & pluginSrc, const char * errStr, UInt32 errCode, bool isError)
|
|
{
|
|
LoadedPlugin plugin = pluginSrc;
|
|
|
|
plugin.errorState = errStr;
|
|
plugin.errorCode = errCode;
|
|
|
|
if(isError)
|
|
m_erroredPlugins.push_back(plugin);
|
|
|
|
_MESSAGE("plugin %s (%08X %s %08X) %s %d (handle %d)",
|
|
plugin.dllName.c_str(),
|
|
plugin.version.dataVersion,
|
|
plugin.version.name,
|
|
plugin.version.pluginVersion,
|
|
plugin.errorState,
|
|
plugin.errorCode,
|
|
s_currentPluginHandle);
|
|
}
|
|
|
|
void PluginManager::ReportPluginErrors()
|
|
{
|
|
#if 0
|
|
PluginErrorDialogBox dialog(*this);
|
|
dialog.Show();
|
|
|
|
return;
|
|
#endif
|
|
|
|
if(m_erroredPlugins.empty())
|
|
return;
|
|
|
|
if(m_oldAddressLibrary)
|
|
UpdateAddressLibraryPrompt();
|
|
|
|
// With this plugin DLL load error, the thread of prophecy is severed. Update your plugins to restore the weave of fate, or persist in the doomed world you have created
|
|
|
|
std::string message = "A DLL plugin has failed to load correctly. If a new version of Skyrim was just released, the plugin needs to be updated. Please check the mod's webpage for updates.\n";
|
|
|
|
for(auto & plugin : m_erroredPlugins)
|
|
{
|
|
message += "\n";
|
|
message += plugin.dllName + ": " + plugin.errorState;
|
|
|
|
if(plugin.errorCode)
|
|
{
|
|
char codeStr[128];
|
|
sprintf_s(codeStr, "%08X", plugin.errorCode);
|
|
|
|
message += " (";
|
|
message += codeStr;
|
|
message += ")";
|
|
}
|
|
}
|
|
|
|
message += "\n\nContinuing to load may result in lost save data or other undesired behavior.";
|
|
message += "\nExit game? (yes highly suggested)";
|
|
|
|
int result = MessageBox(0, message.c_str(), "SKSE Plugin Loader", MB_YESNO);
|
|
|
|
if(result == IDYES)
|
|
{
|
|
TerminateProcess(GetCurrentProcess(), 0);
|
|
}
|
|
}
|
|
|
|
void PluginManager::UpdateAddressLibraryPrompt()
|
|
{
|
|
int result = MessageBox(0,
|
|
"DLL plugins you have installed require a new version of the Address Library. Either this is a new install, or Skyrim was just updated. Visit the Address Library webpage for updates?",
|
|
"SKSE Plugin Loader", MB_YESNO);
|
|
|
|
if(result == IDYES)
|
|
{
|
|
ShellExecute(0, nullptr, "https://www.nexusmods.com/skyrimspecialedition/mods/32444", nullptr, nullptr, 0);
|
|
TerminateProcess(GetCurrentProcess(), 0);
|
|
}
|
|
}
|
|
|
|
void * PluginManager::GetEventDispatcher(UInt32 dispatcherId)
|
|
{
|
|
void * result = nullptr;
|
|
|
|
#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].version.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->version.name, pluginName))
|
|
{
|
|
return idx;
|
|
}
|
|
idx++;
|
|
}
|
|
return kPluginHandle_Invalid;
|
|
}
|
|
|
|
void PluginErrorDialogBox::Show()
|
|
{
|
|
extern HINSTANCE g_moduleHandle;
|
|
|
|
CreateDialogParam(g_moduleHandle, MAKEINTRESOURCE(IDD_PLUGINERROR), NULL, _DialogProc, (LPARAM)this);
|
|
UInt32 err = GetLastError();
|
|
}
|
|
|
|
INT_PTR PluginErrorDialogBox::_DialogProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
INT_PTR result = 0;
|
|
PluginErrorDialogBox * context = nullptr;
|
|
|
|
if(msg == WM_INITDIALOG)
|
|
{
|
|
context = (PluginErrorDialogBox *)lParam;
|
|
context->m_window = window;
|
|
SetWindowLongPtr(window, GWLP_USERDATA, lParam);
|
|
}
|
|
else
|
|
{
|
|
context = (PluginErrorDialogBox *)GetWindowLongPtr(window, GWLP_USERDATA);
|
|
}
|
|
|
|
if(context)
|
|
result = context->DialogProc(msg, wParam, lParam);
|
|
|
|
return result;
|
|
}
|
|
|
|
INT_PTR PluginErrorDialogBox::DialogProc(UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
INT_PTR result = FALSE;
|
|
|
|
switch(msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
result = TRUE;
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
{
|
|
bool done = false;
|
|
|
|
switch(LOWORD(wParam))
|
|
{
|
|
case IDCANCEL:
|
|
done = true;
|
|
m_exitGame = true;
|
|
break;
|
|
|
|
case IDOK:
|
|
done = true;
|
|
break;
|
|
}
|
|
|
|
if(done)
|
|
{
|
|
DestroyWindow(m_window);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|