From 10b87b3bbcf8ec4e5ec44a3c8c09945b8983cf54 Mon Sep 17 00:00:00 2001 From: Marvin D <41352111+IcePixelx@users.noreply.github.com> Date: Sun, 21 Aug 2022 00:59:55 +0200 Subject: [PATCH] PluginSDK init and PluginSystem improvements * Plugins can be loaded now (commented) --- r5dev/core/stdafx.h | 20 +-- r5dev/launcher/IApplication.cpp | 12 ++ r5dev/pluginsdk/dllmain.cpp | 21 +++ r5dev/pluginsdk/ifactory.h | 17 +++ r5dev/pluginsdk/pluginsdk.cpp | 72 ++++++++++ r5dev/pluginsdk/pluginsdk.h | 23 ++++ r5dev/pluginsystem/pluginsystem.cpp | 24 ++-- r5dev/pluginsystem/pluginsystem.h | 5 +- r5dev/vpc/interfaces.cpp | 24 +++- r5dev/vpc/interfaces.h | 6 +- r5dev/vproj/pluginsdk.vcxproj | 189 ++++++++++++++++++++++++++ r5dev/vproj/pluginsdk.vcxproj.filters | 57 ++++++++ r5sdk.sln | 11 ++ 13 files changed, 455 insertions(+), 26 deletions(-) create mode 100644 r5dev/pluginsdk/dllmain.cpp create mode 100644 r5dev/pluginsdk/ifactory.h create mode 100644 r5dev/pluginsdk/pluginsdk.cpp create mode 100644 r5dev/pluginsdk/pluginsdk.h create mode 100644 r5dev/vproj/pluginsdk.vcxproj create mode 100644 r5dev/vproj/pluginsdk.vcxproj.filters diff --git a/r5dev/core/stdafx.h b/r5dev/core/stdafx.h index 5467333f..7b9d44b1 100644 --- a/r5dev/core/stdafx.h +++ b/r5dev/core/stdafx.h @@ -28,9 +28,9 @@ #include #include -#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) +#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) && !defined(PLUGINSDK) #include -#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE +#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE && !PLUGINSDK #include "thirdparty/nlohmann/json.hpp" @@ -53,7 +53,7 @@ #include "launcher/launcherdefs.h" #endif // SDKLAUNCHER -#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) +#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) && !defined(PLUGINSDK) #include "thirdparty/imgui/include/imgui.h" #include "thirdparty/imgui/include/imgui_stdlib.h" #include "thirdparty/imgui/include/imgui_logger.h" @@ -62,12 +62,12 @@ #include "thirdparty/imgui/include/imgui_internal.h" #include "thirdparty/imgui/include/imgui_impl_dx11.h" #include "thirdparty/imgui/include/imgui_impl_win32.h" -#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE +#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE && !PLUGINSDK -#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) +#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) && !defined(PLUGINSDK) #include "thirdparty/lzham/include/lzham_types.h" #include "thirdparty/lzham/include/lzham.h" -#endif // !SDKLAUNCHER && !NETCONSOLE +#endif // !SDKLAUNCHER && !NETCONSOLE && !PLUGINSDK #include "thirdparty/spdlog/include/spdlog.h" #include "thirdparty/spdlog/include/async.h" @@ -93,11 +93,11 @@ #include "tier0/basetypes.h" #include "tier0/platform.h" #include "tier0/commonmacros.h" -#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) +#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) && !defined(PLUGINSDK) #include "tier0/dbg.h" -#endif // !SDKLAUNCHER && !NETCONSOLE +#endif // !SDKLAUNCHER && !NETCONSOLE && !PLUGINSDK -#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) +#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) && !defined(PLUGINSDK) #if !defined (DEDICATED) inline CModule g_GameDll = CModule("r5apex.exe"); inline CModule g_RadVideoToolsDll = CModule("bink2w64.dll"); @@ -121,4 +121,4 @@ ReturnType CallVFunc(int index, void* thisPtr, Args... args) { return (*reinterpret_cast(thisPtr))[index](thisPtr, args...); } -#endif // !SDKLAUNCHER && !NETCONSOLE \ No newline at end of file +#endif // !SDKLAUNCHER && !NETCONSOLE && !PLUGINSDK \ No newline at end of file diff --git a/r5dev/launcher/IApplication.cpp b/r5dev/launcher/IApplication.cpp index 590b5cf8..cd1d3abe 100644 --- a/r5dev/launcher/IApplication.cpp +++ b/r5dev/launcher/IApplication.cpp @@ -10,6 +10,7 @@ #include "tier1/cvar.h" #include "vpc/interfaces.h" #include "launcher/IApplication.h" +#include "pluginsystem/pluginsystem.h" #include "ebisusdk/EbisuSDK.h" #include "engine/cmodel_bsp.h" #include "engine/sys_engine.h" @@ -60,6 +61,17 @@ bool CModAppSystemGroup::Create(CModAppSystemGroup* pModAppSystemGroup) #endif // DEDICATED g_pConCommand->Init(); g_pFactory->GetFactoriesFromRegister(); + g_pFactory->AddFactory(FACTORY_INTERFACE_VERSION, g_pFactory); + g_pFactory->AddFactory(INTERFACEVERSION_PLUGINSYSTEM, g_pPluginSystem); + + // DEBUG CODE FOR PLUGINS + //g_pPluginSystem->PluginSystem_Init(); + //for (auto& it : g_pPluginSystem->GetPluginInstances()) + //{ + // if (g_pPluginSystem->LoadPluginInstance(it)) + // spdlog::info("Load PLUGIN SUCCESS\n"); + //} + #ifndef DEDICATED g_pClientEntityList = g_pFactory->GetFactoryPtr("VClientEntityList003", false).RCast(); diff --git a/r5dev/pluginsdk/dllmain.cpp b/r5dev/pluginsdk/dllmain.cpp new file mode 100644 index 00000000..e3fd08b7 --- /dev/null +++ b/r5dev/pluginsdk/dllmain.cpp @@ -0,0 +1,21 @@ +//=============================================================================// +// +// Purpose: plugin loading, unloading +// +//----------------------------------------------------------------------------- +// +//=============================================================================// + +#include "core/stdafx.h" +#include "pluginsdk.h" + +extern "C" __declspec(dllexport) bool PluginInstance_OnLoad(const char* pszSelfModule) +{ + g_pPluginSDK = new CPluginSDK(pszSelfModule); + return g_pPluginSDK->InitSDK(); +} + +extern "C" __declspec(dllexport) void PluginInstance_OnUnload() +{ + delete g_pPluginSDK; +} \ No newline at end of file diff --git a/r5dev/pluginsdk/ifactory.h b/r5dev/pluginsdk/ifactory.h new file mode 100644 index 00000000..54e01711 --- /dev/null +++ b/r5dev/pluginsdk/ifactory.h @@ -0,0 +1,17 @@ +#pragma once + +struct FactoryInfo_t; + +// TODO: Make this abstract and make it base class of CFactory. +class IFactory +{ +public: + virtual void AddFactory(const string& svFactoryName, void* pFactory) = 0; + virtual void AddFactory(FactoryInfo_t factoryInfo) = 0; + virtual size_t GetVersionIndex(const string& svInterfaceName) const = 0; + virtual void GetFactoriesFromRegister(void) = 0; + virtual CMemory GetFactoryPtr(const string& svFactoryName, bool versionLess = true) const = 0; + virtual const char* GetFactoryFullName(const string& svFactoryName) const = 0; +}; + +constexpr const char* FACTORY_INTERFACE_VERSION = "VFactorySystem001"; \ No newline at end of file diff --git a/r5dev/pluginsdk/pluginsdk.cpp b/r5dev/pluginsdk/pluginsdk.cpp new file mode 100644 index 00000000..2e865b86 --- /dev/null +++ b/r5dev/pluginsdk/pluginsdk.cpp @@ -0,0 +1,72 @@ +//=============================================================================// +// +// Purpose: plugin sdk that makes plugins run! +// +//----------------------------------------------------------------------------- +// +//=============================================================================// + +#include "core/stdafx.h" + +#include "ifactory.h" +#include "pluginsystem/ipluginsystem.h" + +#include "pluginsdk.h" + +//--------------------------------------------------------------------------------- +// Purpose: constructor +// Input : pszSelfModule - +//--------------------------------------------------------------------------------- +CPluginSDK::CPluginSDK(const char* pszSelfModule) : m_FactoryInstance(nullptr), m_PluginSystem(nullptr) +{ + m_SelfModule = CModule(pszSelfModule); + m_GameModule = CModule("r5apex.exe"); + m_SDKModule = CModule("gamesdk.dll"); +} + +//--------------------------------------------------------------------------------- +// Purpose: destructor +//--------------------------------------------------------------------------------- +CPluginSDK::~CPluginSDK() +{ + +} + +//--------------------------------------------------------------------------------- +// Purpose: properly initialize the plugin sdk +//--------------------------------------------------------------------------------- +bool CPluginSDK::InitSDK() +{ + return false; + auto getFactorySystemFn = m_SDKModule.GetExportedFunction("GetFactorySystem").RCast(); + + Assert(getFactorySystemFn, "Could not find GetFactorySystem export from gamesdk.dll"); + if (!getFactorySystemFn) + return false; + + m_FactoryInstance = reinterpret_cast(getFactorySystemFn()); + Assert(getFactorySystemFn, "m_FactoryInstace was nullptr."); + if (!m_FactoryInstance) + return false; + + // Let's make sure the factory version matches, else we unload. + bool isFactoryVersionOk = strcmp(m_FactoryInstance->GetFactoryFullName("VFactorySystem"), FACTORY_INTERFACE_VERSION) == 0; + Assert(isFactoryVersionOk, "Version missmatch between IFactory and CFactory."); + if (!isFactoryVersionOk) + return false; + + // Let's make sure the SDK version matches with the PluginSystem, else we unload + bool isPluginVersionOk = strcmp(m_FactoryInstance->GetFactoryFullName("VPluginSystem"), PLUGINSDK_CLASS_VERSION) == 0; + Assert(isPluginVersionOk, "Version missmatch between CPluginSDK and CPluginSystem."); + if (!isPluginVersionOk) + return false; + + m_PluginSystem = m_FactoryInstance->GetFactoryPtr(PLUGINSDK_CLASS_VERSION, false).RCast(); + Assert(m_PluginSystem, "m_PluginSystem was nullptr."); + if (!m_PluginSystem) + return false; + + return true; +} + +CPluginSDK* g_pPluginSDK = nullptr; \ No newline at end of file diff --git a/r5dev/pluginsdk/pluginsdk.h b/r5dev/pluginsdk/pluginsdk.h new file mode 100644 index 00000000..cae6ab4b --- /dev/null +++ b/r5dev/pluginsdk/pluginsdk.h @@ -0,0 +1,23 @@ +#pragma once + +class IFactory; +class IPluginSystem; +//-----------------------------------------------------------------------------// + +class CPluginSDK +{ +public: + CPluginSDK(const char* pszSelfModule); + ~CPluginSDK(); + + bool InitSDK(); +private: + + IFactory* m_FactoryInstance; + IPluginSystem* m_PluginSystem; + CModule m_SelfModule; + CModule m_GameModule; + CModule m_SDKModule; +}; +constexpr const char* PLUGINSDK_CLASS_VERSION = "VPluginSystem001"; +extern CPluginSDK* g_pPluginSDK; \ No newline at end of file diff --git a/r5dev/pluginsystem/pluginsystem.cpp b/r5dev/pluginsystem/pluginsystem.cpp index 2004a7bc..dae97be4 100644 --- a/r5dev/pluginsystem/pluginsystem.cpp +++ b/r5dev/pluginsystem/pluginsystem.cpp @@ -1,6 +1,6 @@ //=============================================================================// // -// Purpose: Manager that manages Plugins! +// Purpose: plugin system that manages plugins! // //----------------------------------------------------------------------------- // @@ -48,19 +48,21 @@ bool CPluginSystem::LoadPluginInstance(PluginInstance_t& pluginInst) return false; HMODULE loadedPlugin = LoadLibraryA(pluginInst.m_svPluginFullPath.c_str()); - if (loadedPlugin == INVALID_HANDLE_VALUE) + if (loadedPlugin == INVALID_HANDLE_VALUE || loadedPlugin == 0) return false; - pluginInst.m_hModule = CModule(pluginInst.m_svPluginName); + CModule pluginModule = CModule(pluginInst.m_svPluginName); - auto onLoadFn = pluginInst.m_hModule.GetExportedFunction("PluginInstance_OnLoad").RCast(); + auto onLoadFn = pluginModule.GetExportedFunction("PluginInstance_OnLoad").RCast(); Assert(onLoadFn); - onLoadFn(pluginInst.m_hModule, g_GameDll); + if (!onLoadFn(pluginInst.m_svPluginName.c_str())) + { + FreeLibrary(loadedPlugin); + return false; + } - auto getDescFn = pluginInst.m_hModule.GetExportedFunction("PluginInstance_GetDescription").RCast(); - if (getDescFn) - pluginInst.m_svDescription = getDescFn(); + pluginInst.m_hModule = pluginModule; return pluginInst.m_bIsLoaded = true; } @@ -75,11 +77,11 @@ bool CPluginSystem::UnloadPluginInstance(PluginInstance_t& pluginInst) if (!pluginInst.m_bIsLoaded) return false; - Assert(pluginInst.m_hModule.GetModuleBase()); - auto onUnloadFn = pluginInst.m_hModule.GetExportedFunction("PluginInstance_OnUnload").RCast(); + Assert(onUnloadFn); + if (onUnloadFn) - onUnloadFn(g_GameDll); + onUnloadFn(); bool unloadOk = FreeLibrary((HMODULE)pluginInst.m_hModule.GetModuleBase()); Assert(unloadOk); diff --git a/r5dev/pluginsystem/pluginsystem.h b/r5dev/pluginsystem/pluginsystem.h index b3633407..0a707755 100644 --- a/r5dev/pluginsystem/pluginsystem.h +++ b/r5dev/pluginsystem/pluginsystem.h @@ -23,9 +23,8 @@ public: PluginInstance_t(string svPluginName, string svPluginFullPath) : m_svPluginName(svPluginName), m_svPluginFullPath(svPluginFullPath), m_svDescription(std::string()), m_bIsLoaded(false) {}; // Might wanna make a status code system. - typedef void(*OnLoad)(CModule, CModule); - typedef void(*OnUnload)(CModule); - typedef const char* (*GetDescription)(); + typedef bool(*OnLoad)(const char*); + typedef void(*OnUnload)(); CModule m_hModule; string m_svPluginName; diff --git a/r5dev/vpc/interfaces.cpp b/r5dev/vpc/interfaces.cpp index a6262adc..f1749571 100644 --- a/r5dev/vpc/interfaces.cpp +++ b/r5dev/vpc/interfaces.cpp @@ -38,7 +38,7 @@ void CFactory::AddFactory(FactoryInfo_t factoryInfo) size_t CFactory::GetVersionIndex(const string& svInterfaceName) const { size_t nVersionIndex = 0; - for (size_t i = 0; i < svInterfaceName.length(); i++) // Loop through each charater to find the start of interface version. + for (size_t i = 0; i < svInterfaceName.length(); i++) // Loop through each character to find the start of interface version. { if (std::isdigit(svInterfaceName[i])) { @@ -91,4 +91,26 @@ CMemory CFactory::GetFactoryPtr(const string& svFactoryName, bool bVersionLess) return CMemory(); } + +//--------------------------------------------------------------------------------- +// Purpose: get full factory string from versionless string +// Input : svFactoryName - +// Output : const char* +//--------------------------------------------------------------------------------- +const char* CFactory::GetFactoryFullName(const string& svFactoryName) const +{ + for (const FactoryInfo_t& it : m_vFactories) + { + if (it.m_szFactoryName == svFactoryName) + return it.m_szFactoryFullName.c_str(); + } + + return ""; +} + +extern "C" __declspec(dllexport) void* GetFactorySystem() +{ + return g_pFactory; +} + CFactory* g_pFactory = new CFactory(); \ No newline at end of file diff --git a/r5dev/vpc/interfaces.h b/r5dev/vpc/interfaces.h index 9519fe54..b8bb8ca5 100644 --- a/r5dev/vpc/interfaces.h +++ b/r5dev/vpc/interfaces.h @@ -3,6 +3,8 @@ /*----------------------------------------------------------------------------- * _interfaces.h *-----------------------------------------------------------------------------*/ + +// Maybe make them constexpr. #define VENGINE_LAUNCHER_API_VERSION "VENGINE_LAUNCHER_API_VERSION004" #define VENGINE_GAMEUIFUNCS_VERSION "VENGINE_GAMEUIFUNCS_VERSION005" @@ -36,7 +38,8 @@ #define SHADERSYSTEM_INTERFACE_VERSION "ShaderSystem002" #define FILESYSTEM_INTERFACE_VERSION "VFileSystem017" - //----------------------------------------------------------------------------- +#define FACTORY_INTERFACE_VERSION "VFactorySystem001" +//----------------------------------------------------------------------------- enum class InterfaceStatus_t : int { @@ -84,6 +87,7 @@ public: virtual size_t GetVersionIndex(const string& svInterfaceName) const; virtual void GetFactoriesFromRegister(void); virtual CMemory GetFactoryPtr(const string& svFactoryName, bool versionLess = true) const; + virtual const char* GetFactoryFullName(const string& svFactoryName) const; private: vector m_vFactories; diff --git a/r5dev/vproj/pluginsdk.vcxproj b/r5dev/vproj/pluginsdk.vcxproj new file mode 100644 index 00000000..8e4a25fa --- /dev/null +++ b/r5dev/vproj/pluginsdk.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {42214a91-2eef-4717-bd99-6fd7fccf2dbe} + pluginsdk + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + Static + + + DynamicLibrary + false + v143 + true + Unicode + Static + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + pluginsdk_x64 + $(SolutionDir)r5dev\;$(IncludePath); + + + false + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + pluginsdk_x64 + $(SolutionDir)r5dev\;$(IncludePath); + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + core\stdafx.h + /D PLUGINSDK /D _CRT_SECURE_NO_WARNINGS + + + Console + true + $(SolutionDir)lib\$(Configuration)\ + + + IF EXIST "$(SolutionDir)..\..\r5apexdata.bin" del "$(SolutionDir)..\..\bin\x64_plugins\pluginsdk_x64.dll" && copy /Y "$(TargetPath)" "$(SolutionDir)..\..\bin\x64_plugins + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + core\stdafx.h + /D PLUGINSDK /D _CRT_SECURE_NO_WARNINGS + + + Console + true + true + true + $(SolutionDir)lib\$(Configuration)\ + + + IF EXIST "$(SolutionDir)..\..\r5apexdata.bin" del "$(SolutionDir)..\..\bin\x64_plugins\pluginsdk_x64.dll" && copy /Y "$(TargetPath)" "$(SolutionDir)..\..\bin\x64_plugins + + + + + Create + Create + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/r5dev/vproj/pluginsdk.vcxproj.filters b/r5dev/vproj/pluginsdk.vcxproj.filters new file mode 100644 index 00000000..99505c19 --- /dev/null +++ b/r5dev/vproj/pluginsdk.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {8c48f080-f082-47e6-aa17-03316e0430ee} + + + {b1ddea2b-cc6c-413f-b709-d5d436825d04} + + + {49a28380-a137-410c-ad75-a0b80176443e} + + + {f0d21a39-484c-4464-a3fe-a629adc1930e} + + + + + core + + + core + + + sdk + + + public\utility + + + public\utility + + + core + + + + + core + + + sdk + + + sdk + + + public\utility + + + public\utility + + + core + + + \ No newline at end of file diff --git a/r5sdk.sln b/r5sdk.sln index 4d02cab2..5d7a1512 100644 --- a/r5sdk.sln +++ b/r5sdk.sln @@ -82,6 +82,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{3363D141 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcppkore", "r5dev\vproj\libcppkore.vcxproj", "{88BC2D60-A093-4E61-B194-59AB8BE4E33E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pluginsdk", "r5dev\vproj\pluginsdk.vcxproj", "{42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -226,6 +228,14 @@ Global {88BC2D60-A093-4E61-B194-59AB8BE4E33E}.Release|x64.Build.0 = Release|x64 {88BC2D60-A093-4E61-B194-59AB8BE4E33E}.Release|x86.ActiveCfg = Release|Win32 {88BC2D60-A093-4E61-B194-59AB8BE4E33E}.Release|x86.Build.0 = Release|Win32 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Debug|x64.ActiveCfg = Debug|x64 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Debug|x64.Build.0 = Debug|x64 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Debug|x86.ActiveCfg = Debug|Win32 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Debug|x86.Build.0 = Debug|Win32 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Release|x64.ActiveCfg = Release|x64 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Release|x64.Build.0 = Release|x64 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Release|x86.ActiveCfg = Release|Win32 + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -248,6 +258,7 @@ Global {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68} = {9D2825F8-4BEC-4D0A-B125-6390B554D519} {1942083A-03D9-4D76-B644-A3FA2A118A35} = {3363D141-5FD1-4569-B1B0-EC59ABBA5FAC} {88BC2D60-A093-4E61-B194-59AB8BE4E33E} = {9D2825F8-4BEC-4D0A-B125-6390B554D519} + {42214A91-2EEF-4717-BD99-6FD7FCCF2DBE} = {3363D141-5FD1-4569-B1B0-EC59ABBA5FAC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5E5FE02E-6BCE-4BAF-9948-C56476039C3C}