You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden Change 3358916 on 2017/03/22 by Andrew.Grant Merging //Orion/Main to Dev-General (//Orion/Dev-General) #!tests #!rb na Change 3357395 on 2017/03/21 by Daniel.Lamb Added some more custom stats to the cooker. Only cook the english cook culture when we are running local builds. #!rb Trivial #!test Iterative shared cooked builds paragon Change 3357377 on 2017/03/21 by Daniel.Lamb Added support for packages which fail to load to the package dependency info module #!rb Trivial #!test Cook paragon Change 3356838 on 2017/03/21 by Andrew.Grant Merging //Orion/Main to Dev-General (//Orion/Dev-General) #!3rb #!tests na Change 3355306 on 2017/03/20 by Daniel.Lamb Switched PackageDependencyInfo to using Guid instead of entire package hash when generating dependency info. Stopped cooker from collecting garbage while in the editor. Iterative cooks don't resolve string asset references for startup packages. #!rb Trivial #!test Shared precooked build paragon Change 3354527 on 2017/03/20 by Wes.Hunt AnalyticsProvider::SetUserID will now flush any pending events before changing the ID. #!jira AN-1660 #!fyi josh.markiewicz,david.nikdel #!rb josh.markiewicz #!tests ran client connected to Solo vs. AI server Change 3353852 on 2017/03/20 by Benn.Gallagher Speculative fix for clothing crashes using Mambo. It was possible that the skeletal mesh component could have triggered deletion or creation of simulation state objects while the simulation was in flight on another thread, added tracking and waiting for outstanding tasks. #!jira OR-36843, UE-42975 #!rb Martin.Wilson #!tests Editor PIE, -game hero gallery Change 3353048 on 2017/03/18 by Jeff.Williams #!ORION_DG - Merge MAIN @CL 3353033 Change 3352845 on 2017/03/17 by Daniel.Lamb Renamed the ConvertRenderTargetToTexture2D function so that it's obvious it's a editor only feature. #!rb Daniel.Wright #!test Editor paragon Change 3352544 on 2017/03/17 by Daniel.Lamb ADded support for ignoring ini settings incompatbilities when using shared cooked builds. #!rb Trivial #!test Shared cooked build paragon Change 3352285 on 2017/03/17 by Daniel.Lamb Fix client side compilation error to do with render texture conversion function #!rb Trivial #!test Compile Paragon Change 3352141 on 2017/03/17 by Daniel.Lamb Added support for blueprint function to convert a rendertexture to a texture. #!rb Daniel.Wright #!test Run in the editor Change 3351612 on 2017/03/17 by Andrew.Grant Expand EngineDir and ProjectDir variables during AppLocal deployment #!tests Jamie verified packaging Orion via the editor works now #!rb Jamie.Dale Change 3350470 on 2017/03/16 by Laurent.Delayen Fix for PS4 compile. #!rb none #!tests PS4 + non unity Change 3350237 on 2017/03/16 by Andrew.Grant Pak-mounting fix from Dev-Core for OR-36896 #!tests na #!rb GIl.Gribb Change 3350079 on 2017/03/16 by Laurent.Delayen Added 'AnimNotify_PlayMontageNotify' and 'AnimNotify_PlayMontageNotifyWindow' to forward notifies Begin/End to 'PlayMontage' AsyncTask. #!rb lina.halper #!tests Yin's BP Change3349694on 2017/03/16 by robomerge #!ROBOMERGE-AUTHOR: dan.hertzka Exposing copy/paste actions for properties embedded within IDetailGroup header rows #!rb Matt.Kuhlenschmidt #!tests Copy/paste on skin variant primary override rows #!ROBOMERGE-SOURCE: CL 3349513 in //Orion/Dev-REGS/... via CL 3349675 #!ROBOMERGE-BOT: ORION (Main -> Dev-General) Change 3349560 on 2017/03/16 by David.Ratti Update GameplayTagReferenceHelper to pass in raw data for owner struct (Rather than having caller pass raw 'this' to delegate). Fixes crashes with resizing lists while making calling code less crappy (avoid having to implement copy cstor and operator to fixup delegate). Added GameplayTagReferenceHelper to gameplay cue classes. #!rb none #!tests editor Change 3349305 on 2017/03/16 by Andrew.Grant Merging //Orion/Main to Dev-General (//Orion/Dev-General) #!tests compiled #!rb na Change 3349189 on 2017/03/16 by Benn.Gallagher Fixed clothing not running in PS4 packaged builds #!rb Martin.Wilson #!jira OR-36680 #!tests PS4 cooked OrionEntry with Shinbi Change 3348659 on 2017/03/15 by Daniel.Lamb Fix compilation errors. #!rb None Change 3348646 on 2017/03/15 by Andrew.Grant Unshelved from pending changelist '3347778': <description: restricted, no permission to view> Change 3348636 on 2017/03/15 by Daniel.Lamb Fixed issue with rebuildlighting commandlet not checking out separate lighting files. #!rb None #!test ResavePackages commandlet Change 3348559 on 2017/03/15 by Daniel.Lamb Fixed up some iterative ini settings blacklist configs. #!rb Trivial #!test Iterative Cook paragon Change 3348379 on 2017/03/15 by Laurent.Delayen Added simple Async Node 'Play Montage' to use outside of gameplay abilities. #!rb none #!tests none Change 3348035 on 2017/03/15 by Ben.Salem Switch automationcheckpoint to being a .log file. Unblocks running on packaged builds in paragon. #!rb none #!tests ran oh so very many tests with the changes. Change 3345982 on 2017/03/14 by Zak.Middleton #!orion - OR-36422: Clamp client net send rate for character movement to 60Hz (down from 90). Integrates CL 3345771 from Dev-Framework which adds engine support for specifying the rate parameters, and sets them in Orion DefaultGame.ini to 1/60 second. #!jira OR-36422 #!tests multi-PIE dedicated server, various framerates, net lag, etc. #!rb Laurent.Delayen #!codereview Laurent.Delayen Change 3345134 on 2017/03/14 by Jordan.Walker mono work Change 3344857 on 2017/03/14 by Martin.Wilson Missing includes for transactor header #!rb none Change 3341860 on 2017/03/10 by Chris.Bunner Partial revert of CL 3339904. Fixed material translation error with multiple connections from custom interpolator nodes. #!rb None #!tests Editor, Known trouble materials with interpolator nodes, With/without material functions Change 3341759 on 2017/03/10 by Daniel.Lamb Fixed up NetworkCompatible version so that it works with UGS. #!rb Trivial #!test Cook ps4 paragon. Change 3341616 on 2017/03/10 by Josh.Markiewicz #!UE4 - added define for OGS feature #!rb none #!codereview sam.zamani #!tests compiles Change 3341612 on 2017/03/10 by Josh.Markiewicz #!UE4 - removed old define #!tests compiles Change 3340180 on 2017/03/09 by Daniel.Lamb Integrate fix for sync loading from main to Dev General. #!rb Ben.Zeigler Change 3339904 on 2017/03/09 by Chris.Bunner Fixed material translation error when custom interpolator node hooked to multiple function outputs. #!rb None #!tests Editor Change 3339280 on 2017/03/09 by Josh.Markiewicz #!UE4 - removed WebBrowser moduel dependency on OnlineSubsystem - added 2 functions to online engine interface #!codereview sam.zamani, ben.marsh Change 3338654 on 2017/03/08 by Daniel.Lamb Fixed up some issues with iterative ini settings. Added support for target platforms exposing which audio formats they use so they can match up supported formats with different machines. #!rb None #!test Cook paragon iteratively Change 3336989 on 2017/03/08 by Ben.Marsh Merging CL 3336693 from Dev-Core: Use shared PCHs for game plugins by default, to reduce time spent generating individual PCHs. #!rb none Change 3336135 on 2017/03/07 by Michael.Trepka Hide GameLayerManager's title bar on exiting PIE #!rb Dan.Hertzka #!tests Tested in the editor on Windows Change 3335324 on 2017/03/07 by Aaron.Eady Chat; Adding AddedItem, CompletedItem, and DiscardedItem to the chat message type enum so we can control the color for each. Set the colors in the Social asset. Creating client record settings for turning on/off the added item, completed item, and discarded item in chat. Put these in the gameplay settings menu. Added horizontal boxes to the gameplay settings menu because we are running out of space. Added a vertical scroll bar to the gameplay settings menu but it doesn't seem to show. Also fixed the horizontal scroll bar at the bottom to be horizontal instead of vertical. #!rb Matt.Schembari #!tests MCP, PIE #!lockdown Nicholas.Davies #!RN Change 3333541 on 2017/03/06 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3333512 #!RB:none #!Tests:none #!codeReview: cameron.winston Change 3332578 on 2017/03/04 by Andrew.Grant Temp Disabled wrong-looking warning #!tests #!rb na #!ROBOMERGE: Main Change 3332555 on 2017/03/04 by Andrew.Grant Proper fix for Tencent DLL issue #!tests #!rb na #!ROBOMERGE: Main Change 3332552 on 2017/03/04 by Andrew.Grant Fix for Tencent DLL issue while staging #!tests none #!rb none #!ROBOMERGE: Main Change 3332216 on 2017/03/03 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3332168 #!RB:none #!Tests:none Change 3332060 on 2017/03/03 by Daniel.Lamb Fixed issue with AsyncLoading code eventually flushing async loading while in async loading... This causes all kinds of cool stuff like objects on the stack corruption and also deleted memory accesses. #!rb Gil.Gribb. #!test Editor and -game Change 3331680 on 2017/03/03 by Jason.Bestimt #!ORION_MAIN - Merge MAIN @ CL 3331636 #!RB:none #!Tests:none #!codeReview: andrew.grant Change 3331412 on 2017/03/03 by James.Hopkin #!orion Rebuilt OpenSSL libs for PS4 to fix process termination due to SIGPIPE on closing websockets Source change committed in CL#!3331380 #!jira OR-36274 #!fyi Paul.Moore Change 3331375 on 2017/03/03 by Sam.Zamani fix dll path for tenproxy #!rb none #!tests none Change 3330953 on 2017/03/02 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3330924 [STOMPED ChestOpeningScreen.uasset] #!RB:none #!Tests:none #!codeReview: bryan.rathman, phil.buuck, matt.schembari, andrew.grant Change 3330646 on 2017/03/02 by Andrew.Grant Warning and non-unity fix #!tests compiled #!rb none Change 3330388 on 2017/03/02 by Andrew.Grant Merging //Orion/Main to Dev-General (//Orion/Dev-General) #!tests #!rb na Change 3329982 on 2017/03/02 by Sam.Zamani fixed updated module rules #!rb none #!tests regen projects Change 3329964 on 2017/03/02 by Sam.Zamani Copying //Tasks/Orion/Dev-Online-Tencent to Dev-General (//Orion/Dev-General) 3245325 Adding new OSS for Tencent online platform 3245448 tencent third party SDK TCLS proxy functionality #!rb none 3245474 missing include #!rb none 3249585 TCLS tenproxy.dll in thirdparty bin folder #!rb none 3249726 Load TenProxy.dll for TCLS integration New OSS Tencent #!rb none 3255571 tencent configs #!rb none 3255826 Tencent TCLS paragon launcher #!rb none 3256168 TCLS launch batch update cmd line options #!rb none 3256170 Added "TencentLive,TencentDev" MCP config entries #!rb none3256504xmpp config update #!rb none 3273168 skip login steps for tencent config update #!rb none 3279427 #!xmpp add option to use plain text auth 3279428 disable ssl and use plain text auth for XMPP connection temporary until we have a valid cert setup on Tigase deployment 3281566 enabled OSS tencent this will also be the toggle for detecting when to enable tencent functionality at runtime 3283103 differentiate between tencent dev/live environments disable QoS region selection for tencentdev 3283106 lower http verbosity 3283734 config updates 3285066 disable replays and mtx for tencent build 3291005 #!online,mcp service config bEnabled flag to toggle individual services as needed 3291006 explicitly mark unneeded Mcp services as disabled 3291108 allow replay tab to be disabled via UOrionRuntimeOptions.bEnableReplays=false 3291492 disable recording of replays for tencent mode 3292750 disable replay tab based on bEnableReplays=false 3292753 new orion runtime option bDisallowCoinPurchases if true, prevents coins from being available for purchase 3292755 diable mtx coin offers if bDisallowCoinPurchases=true 3292759 missing header 3293246 disable query for available friend codes if bEnableFriendCodes=false 3293250 temp usage of NULL analytics provider 3298025 Adding optional RegionTencent plugin for overriding config files 3298027 ability to override config cache values via plugin config files 3311016 default to TencentDev backend when running in tencent mode 3311017 CMS tencent config 3311022 Rename RegionTencent to RegionCN 3312470 disable links for tencent build 3313014 move tenproxy.dll to \OrionGame\Binaries\ThirdParty\Tencent 3314861 tenproxy 2.0.2.7 update 3314878 default RegionCN plugin to disabled this will only be enabled once the RegionCN.pak is loaded 3314879 TCLS launcher pointing at UE4Editor.exe for development 3315257 missing file 3323573 remove TCLS launcher 3326006 Tencent TLOG SDK 3326277 wrapper singleton class for tenproxy connection 3329180 Tencent support for login flow 3329181 WIP tenproxy connection usage in identity 3329624 wip tcls proxy #!rb none #!tests none Change 3329651 on 2017/03/02 by Andrew.Grant Merging from //UE4/Main @ 3322856 through Orion-Staging #!tests QA #!rb na Change 3329411 on 2017/03/02 by robomerge #!ROBOMERGE-AUTHOR: dan.hertzka Duplicating CL 3303733 from Dev-Editor (simple fix for a massive issue) - This will prevent any TAssetPtr property from getting stomped by undo/redo (you know those ridiculous store and card art issues? Fixed!) #!lockdown Jason.Bestimt #!rb none #!tests Undo on an item definition asset #!ROBOMERGE-SOURCE: CL 3329404 in //Orion/Release-38.3/... via CL 3329405 #!ROBOMERGE-BOT: ORION (Main -> Dev-General) Change 3328858 on 2017/03/01 by Lina.Halper Fixed crash on importing animation that was edited before #!rb: none #!tests: reimport Change 3328459 on 2017/03/01 by Daniel.Lamb When adding new ddc back ends to the hierarchcial ddc make sure to update the async backends lists. #!codereview Gil.Gribb #!test None #!rb Trivial Change 3328182 on 2017/03/01 by Daniel.Lamb Unshelved from pending changelist '3318009': Adding support for shared cooked builds to be downloaded from the network. Included CookedAssetRegistry in the p:\ published builds. #!rb Ben.Marsh Change 3327856 on 2017/03/01 by Frank.Gigliotti Added velocity overrides to FRK4SpringInterpolator; #!RB None #!codeReview Laurent.Delayen #!Tests PIE Change 3327096 on 2017/03/01 by David.Ratti Added generic reference viewer details customization for gameplay tags. Added it to GameplayStatsMetaData. #!rb none #!tests editor Change 3326177 on 2017/02/28 by Daniel.Lamb Added some more debugging information to help track down live issue. #!rb Chris.Bunner #!test Ran editor. Change 3324951 on 2017/02/28 by David.Ratti UDataTable: added AddRow/RemoveRow native functions. #!rb JB #!tests na Change 3323852 on 2017/02/27 by David.Ratti Fix ::RequestAllGameplayTags OnlyIncludeDictionaryTags option #!codereview Ben.Zeigler #!rb #!tests na Change 3323706 on 2017/02/27 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3323694 #!RB:none #!Tests:none Change 3321945 on 2017/02/24 by Jon.Lietz OR-36258 - fixing an issue where gameplay effects that are set to not refresh the period should not allow the execution of a period effect on application. #!RB David.Ratti #!tests golden path #!codeReview: Billy.Bramer, Fred.Kimberley #!RNX Change 3321876 on 2017/02/24 by Daniel.Lamb Fixed erroronEngineContentUse flag not being set properly. #!rb Trivial #!test Cook Paragon. Change 3321591 on 2017/02/24 by Jason.Bestimt #!ORION_DG - MAIN @ CL 3321563 #!RB:none #!Tests:none Change 3321260 on 2017/02/24 by Andrew.Grant Fixed issue that was causing missing string references to not show their referencer #!rb none Change 3321040 on 2017/02/24 by Robert.Manuszewski Merging changes 3316253 and 3319134 from Dev-Core: fixes to file log hangs and crashes. #!rb none #!tests Cooked Win64 server and client, played cooked Win64 build Change 3319413 on 2017/02/23 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3319394 #!RB:none #!Tests:none Change 3317905 on 2017/02/22 by Daniel.Lamb Integrate CL 3238291 from Odin Add Plugin content to the asset registry Change the location of AssetRegistry.bin when cooking a plugin as DLC Include AssetRegistry.bin in the cooked plugin staging process Add function to PluginManager to keep list of any plugins that loaded a pak file Use list of plugins with pak files to merge their AssetRegistry.bin files into the main AssetRegistry when it's created #!rb Ben.Marsh #!codereview Chance.Ivey, Daniel.Lamb Change 3317648 on 2017/02/22 by Cody.Haskell Instead of popping an external web browser, we use the SWebBrowser widget on GFN. #!rb DanH #!codereview Andrew.Grant, Dan.Hertzka, Matt.Schembari #!tests PIE Change 3317289 on 2017/02/22 by Jason.Bestimt #!ORION_DG - Merge MAIN @ CL 3317254 #!RB:none #!Tests:none Change 3317186 on 2017/02/22 by Mieszko.Zielinski Fixed items that have been force-scored by an EQS test as 'failed' getting discarted even if the test is being run in scoring-only mode #!UE4 #!test golden path #!rb Lukasz.Furman #!codereview Daniel.Broder, John.Abercrombie Change 3317005 on 2017/02/22 by Daniel.Lamb Submitted wrong version of my file. #!rb Trivial #!test Compile Change 3316958 on 2017/02/22 by Daniel.Lamb Added support in buildcookrun for shared cooked builds. #!rb Trivial #!test BuildCookRun iterative script Change 3316942 on 2017/02/22 by Daniel.Lamb DLC cooking optimization. Optimization to determining package dependency tree, now is async. Fixes for iterate shared cooked build. Added fallback when using shared cooked build to local build if local build is newer. Added DLC cooking warning if you are overriding output directories. Removed previous release packages names from DLC asset registry. Only generate manifest for additional assets instead of all assets. Minor optimization to worst case resolving of string asset references. Only resolve those that haven't been resolved before (only happens when GC thrashing happens). #!rb Andrew.Grant #!test Cook paragon [CL 3365166 by Andrew Grant in Main branch]
925 lines
30 KiB
C++
925 lines
30 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PluginManager.h"
|
|
#include "GenericPlatform/GenericPlatformFile.h"
|
|
#include "HAL/PlatformFilemanager.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/EngineVersion.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "ProjectDescriptor.h"
|
|
#include "Interfaces/IProjectManager.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "ProjectManager.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC( LogPluginManager, Log, All );
|
|
|
|
#define LOCTEXT_NAMESPACE "PluginManager"
|
|
|
|
|
|
namespace PluginSystemDefs
|
|
{
|
|
/** File extension of plugin descriptor files.
|
|
NOTE: This constant exists in UnrealBuildTool code as well. */
|
|
static const TCHAR PluginDescriptorFileExtension[] = TEXT( ".uplugin" );
|
|
|
|
/**
|
|
* Parsing the command line and loads any foreign plugins that were
|
|
* specified using the -PLUGIN= command.
|
|
*
|
|
* @param CommandLine The commandline used to launch the editor.
|
|
* @param SearchPathsOut
|
|
* @return The number of plugins that were specified using the -PLUGIN param.
|
|
*/
|
|
static int32 GetAdditionalPluginPaths(TSet<FString>& PluginPathsOut)
|
|
{
|
|
const TCHAR* SwitchStr = TEXT("PLUGIN=");
|
|
const int32 SwitchLen = FCString::Strlen(SwitchStr);
|
|
|
|
int32 PluginCount = 0;
|
|
|
|
const TCHAR* SearchStr = FCommandLine::Get();
|
|
do
|
|
{
|
|
FString PluginPath;
|
|
|
|
SearchStr = FCString::Strifind(SearchStr, SwitchStr);
|
|
if (FParse::Value(SearchStr, SwitchStr, PluginPath))
|
|
{
|
|
FString PluginDir = FPaths::GetPath(PluginPath);
|
|
PluginPathsOut.Add(PluginDir);
|
|
|
|
++PluginCount;
|
|
SearchStr += SwitchLen + PluginPath.Len();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
} while (SearchStr != nullptr);
|
|
|
|
#if IS_PROGRAM
|
|
// For programs that have the project dir set, look for plugins under the project directory
|
|
const FProjectDescriptor *Project = IProjectManager::Get().GetCurrentProject();
|
|
if (Project != nullptr)
|
|
{
|
|
PluginPathsOut.Add(FPaths::GetPath(FPaths::GetProjectFilePath()) / TEXT("Plugins"));
|
|
}
|
|
#endif
|
|
return PluginCount;
|
|
}
|
|
}
|
|
|
|
FPlugin::FPlugin(const FString& InFileName, const FPluginDescriptor& InDescriptor, EPluginLoadedFrom InLoadedFrom)
|
|
: Name(FPaths::GetBaseFilename(InFileName))
|
|
, FileName(InFileName)
|
|
, Descriptor(InDescriptor)
|
|
, LoadedFrom(InLoadedFrom)
|
|
, bEnabled(false)
|
|
{
|
|
|
|
}
|
|
|
|
FPlugin::~FPlugin()
|
|
{
|
|
}
|
|
|
|
FString FPlugin::GetName() const
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
FString FPlugin::GetDescriptorFileName() const
|
|
{
|
|
return FileName;
|
|
}
|
|
|
|
FString FPlugin::GetBaseDir() const
|
|
{
|
|
return FPaths::GetPath(FileName);
|
|
}
|
|
|
|
FString FPlugin::GetContentDir() const
|
|
{
|
|
return FPaths::GetPath(FileName) / TEXT("Content");
|
|
}
|
|
|
|
FString FPlugin::GetMountedAssetPath() const
|
|
{
|
|
return FString::Printf(TEXT("/%s/"), *Name);
|
|
}
|
|
|
|
bool FPlugin::IsEnabled() const
|
|
{
|
|
return bEnabled;
|
|
}
|
|
|
|
bool FPlugin::CanContainContent() const
|
|
{
|
|
return Descriptor.bCanContainContent;
|
|
}
|
|
|
|
EPluginLoadedFrom FPlugin::GetLoadedFrom() const
|
|
{
|
|
return LoadedFrom;
|
|
}
|
|
|
|
const FPluginDescriptor& FPlugin::GetDescriptor() const
|
|
{
|
|
return Descriptor;
|
|
}
|
|
|
|
bool FPlugin::UpdateDescriptor(const FPluginDescriptor& NewDescriptor, FText& OutFailReason)
|
|
{
|
|
if(!NewDescriptor.Save(FileName, LoadedFrom == EPluginLoadedFrom::GameProject, OutFailReason))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Descriptor = NewDescriptor;
|
|
return true;
|
|
}
|
|
|
|
FPluginManager::FPluginManager()
|
|
: bHaveConfiguredEnabledPlugins(false)
|
|
, bHaveAllRequiredPlugins(false)
|
|
{
|
|
DiscoverAllPlugins();
|
|
}
|
|
|
|
FPluginManager::~FPluginManager()
|
|
{
|
|
// NOTE: All plugins and modules should be cleaned up or abandoned by this point
|
|
|
|
// @todo plugin: Really, we should "reboot" module manager's unloading code so that it remembers at which startup phase
|
|
// modules were loaded in, so that we can shut groups of modules down (in reverse-load order) at the various counterpart
|
|
// shutdown phases. This will fix issues where modules that are loaded after game modules are shutdown AFTER many engine
|
|
// systems are already killed (like GConfig.) Currently the only workaround is to listen to global exit events, or to
|
|
// explicitly unload your module somewhere. We should be able to handle most cases automatically though!
|
|
}
|
|
|
|
void FPluginManager::RefreshPluginsList()
|
|
{
|
|
// Read a new list of all plugins
|
|
TMap<FString, TSharedRef<FPlugin>> NewPlugins;
|
|
ReadAllPlugins(NewPlugins, PluginDiscoveryPaths);
|
|
|
|
// Build a list of filenames for plugins which are enabled, and remove the rest
|
|
TArray<FString> EnabledPluginFileNames;
|
|
for(TMap<FString, TSharedRef<FPlugin>>::TIterator Iter(AllPlugins); Iter; ++Iter)
|
|
{
|
|
const TSharedRef<FPlugin>& Plugin = Iter.Value();
|
|
if(Plugin->bEnabled)
|
|
{
|
|
EnabledPluginFileNames.Add(Plugin->FileName);
|
|
}
|
|
else
|
|
{
|
|
Iter.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
// Add all the plugins which aren't already enabled
|
|
for(const TPair<FString, TSharedRef<FPlugin>>& NewPluginPair: NewPlugins)
|
|
{
|
|
const TSharedRef<FPlugin>& NewPlugin = NewPluginPair.Value;
|
|
if(!EnabledPluginFileNames.Contains(NewPlugin->FileName))
|
|
{
|
|
AllPlugins.Add(NewPlugin->GetName(), NewPlugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPluginManager::DiscoverAllPlugins()
|
|
{
|
|
ensure( AllPlugins.Num() == 0 ); // Should not have already been initialized!
|
|
|
|
PluginSystemDefs::GetAdditionalPluginPaths(PluginDiscoveryPaths);
|
|
ReadAllPlugins(AllPlugins, PluginDiscoveryPaths);
|
|
}
|
|
|
|
void FPluginManager::ReadAllPlugins(TMap<FString, TSharedRef<FPlugin>>& Plugins, const TSet<FString>& ExtraSearchPaths)
|
|
{
|
|
#if (WITH_ENGINE && !IS_PROGRAM) || WITH_PLUGIN_SUPPORT
|
|
// Find "built-in" plugins. That is, plugins situated right within the Engine directory.
|
|
ReadPluginsInDirectory(FPaths::EnginePluginsDir(), EPluginLoadedFrom::Engine, Plugins);
|
|
|
|
// Find plugins in the game project directory (<MyGameProject>/Plugins). If there are any engine plugins matching the name of a game plugin,
|
|
// assume that the game plugin version is preferred.
|
|
if( FApp::HasGameName() )
|
|
{
|
|
ReadPluginsInDirectory(FPaths::GamePluginsDir(), EPluginLoadedFrom::GameProject, Plugins);
|
|
}
|
|
|
|
const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject();
|
|
if (Project != nullptr)
|
|
{
|
|
// If they have a list of additional directories to check, add those plugins too
|
|
for (const FString& Dir : Project->GetAdditionalPluginDirectories())
|
|
{
|
|
ReadPluginsInDirectory(Dir, EPluginLoadedFrom::Engine, Plugins);
|
|
}
|
|
}
|
|
|
|
for (const FString& ExtraSearchPath : ExtraSearchPaths)
|
|
{
|
|
ReadPluginsInDirectory(ExtraSearchPath, EPluginLoadedFrom::GameProject, Plugins);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FPluginManager::ReadPluginsInDirectory(const FString& PluginsDirectory, const EPluginLoadedFrom LoadedFrom, TMap<FString, TSharedRef<FPlugin>>& Plugins)
|
|
{
|
|
// Make sure the directory even exists
|
|
if(FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*PluginsDirectory))
|
|
{
|
|
TArray<FString> FileNames;
|
|
FindPluginsInDirectory(PluginsDirectory, FileNames);
|
|
|
|
for(const FString& FileName: FileNames)
|
|
{
|
|
FPluginDescriptor Descriptor;
|
|
FText FailureReason;
|
|
if ( Descriptor.Load(FileName, LoadedFrom == EPluginLoadedFrom::GameProject, FailureReason) )
|
|
{
|
|
TSharedRef<FPlugin> Plugin = MakeShareable(new FPlugin(FileName, Descriptor, LoadedFrom));
|
|
|
|
FString FullPath = FPaths::ConvertRelativePathToFull(FileName);
|
|
UE_LOG(LogPluginManager, Verbose, TEXT("Read plugin descriptor for %s, from %s"), *Plugin->GetName(), *FullPath);
|
|
|
|
const TSharedRef<FPlugin>* ExistingPlugin = Plugins.Find(Plugin->GetName());
|
|
if (ExistingPlugin == nullptr)
|
|
{
|
|
Plugins.Add(Plugin->GetName(), Plugin);
|
|
}
|
|
else if ((*ExistingPlugin)->LoadedFrom == EPluginLoadedFrom::Engine && LoadedFrom == EPluginLoadedFrom::GameProject)
|
|
{
|
|
Plugins[Plugin->GetName()] = Plugin;
|
|
UE_LOG(LogPluginManager, Verbose, TEXT("Replacing engine version of '%s' plugin with game version"), *Plugin->GetName());
|
|
}
|
|
else if( (*ExistingPlugin)->LoadedFrom != EPluginLoadedFrom::GameProject || LoadedFrom != EPluginLoadedFrom::Engine)
|
|
{
|
|
UE_LOG(LogPluginManager, Warning, TEXT("Plugin '%s' exists at '%s' and '%s' - second location will be ignored"), *Plugin->GetName(), *(*ExistingPlugin)->FileName, *Plugin->FileName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NOTE: Even though loading of this plugin failed, we'll keep processing other plugins
|
|
FString FullPath = FPaths::ConvertRelativePathToFull(FileName);
|
|
FText FailureMessage = FText::Format(LOCTEXT("FailureFormat", "{0} ({1})"), FailureReason, FText::FromString(FullPath));
|
|
FText DialogTitle = LOCTEXT("PluginFailureTitle", "Failed to load Plugin");
|
|
UE_LOG(LogPluginManager, Error, TEXT("%s"), *FailureMessage.ToString());
|
|
FMessageDialog::Open(EAppMsgType::Ok, FailureMessage, &DialogTitle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPluginManager::FindPluginsInDirectory(const FString& PluginsDirectory, TArray<FString>& FileNames)
|
|
{
|
|
// Class to enumerate the contents of a directory, and find all sub-directories and plugin descriptors within it
|
|
struct FPluginDirectoryVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
TArray<FString> SubDirectories;
|
|
TArray<FString> PluginDescriptors;
|
|
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
|
|
{
|
|
FString FilenameOrDirectoryStr = FilenameOrDirectory;
|
|
if(bIsDirectory)
|
|
{
|
|
SubDirectories.Add(FilenameOrDirectoryStr);
|
|
}
|
|
else if(FilenameOrDirectoryStr.EndsWith(TEXT(".uplugin")))
|
|
{
|
|
PluginDescriptors.Add(FilenameOrDirectoryStr);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Enumerate the contents of the current directory
|
|
FPluginDirectoryVisitor Visitor;
|
|
FPlatformFileManager::Get().GetPlatformFile().IterateDirectory(*PluginsDirectory, Visitor);
|
|
|
|
// If there's no plugins in this directory, recurse through all the child directories
|
|
if(Visitor.PluginDescriptors.Num() == 0)
|
|
{
|
|
for(const FString& SubDirectory: Visitor.SubDirectories)
|
|
{
|
|
FindPluginsInDirectory(SubDirectory, FileNames);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(const FString& PluginDescriptor: Visitor.PluginDescriptors)
|
|
{
|
|
if (!FileNames.Contains(PluginDescriptor))
|
|
{
|
|
FileNames.Add(PluginDescriptor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper class to find all pak files.
|
|
class FPakFileSearchVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
TArray<FString>& FoundFiles;
|
|
public:
|
|
FPakFileSearchVisitor(TArray<FString>& InFoundFiles)
|
|
: FoundFiles(InFoundFiles)
|
|
{}
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory)
|
|
{
|
|
if (bIsDirectory == false)
|
|
{
|
|
FString Filename(FilenameOrDirectory);
|
|
if (Filename.MatchesWildcard(TEXT("*.pak")) && !FoundFiles.Contains(Filename))
|
|
{
|
|
FoundFiles.Add(Filename);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
bool FPluginManager::IsPluginSupportedByCurrentTarget(TSharedRef<FPlugin> Plugin) const
|
|
{
|
|
bool bSupported = false;
|
|
if (Plugin->GetDescriptor().Modules.Num())
|
|
{
|
|
for (const FModuleDescriptor& Module : Plugin->GetDescriptor().Modules)
|
|
{
|
|
// Programs support only program type plugins
|
|
// Non-program targets don't support from plugins
|
|
#if IS_PROGRAM
|
|
if (Module.Type == EHostType::Program)
|
|
{
|
|
bSupported = true;
|
|
}
|
|
#else
|
|
if (Module.Type != EHostType::Program)
|
|
{
|
|
bSupported = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSupported = true;
|
|
}
|
|
return bSupported;
|
|
}
|
|
|
|
bool FPluginManager::ConfigureEnabledPlugins()
|
|
{
|
|
if(!bHaveConfiguredEnabledPlugins)
|
|
{
|
|
// Don't need to run this again
|
|
bHaveConfiguredEnabledPlugins = true;
|
|
|
|
// If a current project is set, check that we know about any plugin that's explicitly enabled
|
|
const FProjectDescriptor *Project = IProjectManager::Get().GetCurrentProject();
|
|
const bool bHasProjectFile = Project != nullptr;
|
|
|
|
// Get all the enabled plugin names
|
|
TArray< FString > EnabledPluginNames;
|
|
#if IS_PROGRAM
|
|
// Programs can also define the list of enabled plugins in ini
|
|
GConfig->GetArray(TEXT("Plugins"), TEXT("ProgramEnabledPlugins"), EnabledPluginNames, GEngineIni);
|
|
#endif
|
|
#if !IS_PROGRAM || HACK_HEADER_GENERATOR
|
|
if (!FParse::Param(FCommandLine::Get(), TEXT("NoEnginePlugins")))
|
|
{
|
|
FProjectManager::Get().GetEnabledPlugins(EnabledPluginNames);
|
|
}
|
|
#endif
|
|
|
|
// Build a set from the array
|
|
TSet< FString > AllEnabledPlugins;
|
|
AllEnabledPlugins.Append(MoveTemp(EnabledPluginNames));
|
|
|
|
// Enable all the plugins by name
|
|
for (const TPair<FString, TSharedRef< FPlugin >> PluginPair : AllPlugins)
|
|
{
|
|
const TSharedRef<FPlugin>& Plugin = PluginPair.Value;
|
|
if (AllEnabledPlugins.Contains(Plugin->Name))
|
|
{
|
|
#if IS_PROGRAM
|
|
Plugin->bEnabled = !bHasProjectFile || IsPluginSupportedByCurrentTarget(Plugin);
|
|
if (!Plugin->bEnabled)
|
|
{
|
|
AllEnabledPlugins.Remove(Plugin->Name);
|
|
}
|
|
#else
|
|
Plugin->bEnabled = true;
|
|
#endif
|
|
|
|
if (Plugin->bEnabled && FPlatformMisc::ShouldDisablePluginAtRuntime(Plugin->Name))
|
|
{
|
|
Plugin->bEnabled = false;
|
|
AllEnabledPlugins.Remove(Plugin->Name);
|
|
}
|
|
|
|
if (Plugin->bEnabled && Plugin->Descriptor.CompatibleChangelist != 0 && FEngineVersion::CompatibleWith().HasChangelist() && Plugin->Descriptor.CompatibleChangelist != FEngineVersion::CompatibleWith().GetChangelist())
|
|
{
|
|
FText Title = LOCTEXT("IncompatiblePluginTitle", "Incompatible Plugin");
|
|
if (FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("IncompatiblePluginMessage", "'{0}' was designed for a different version of the game. Attempt to load it anyway?"), FText::FromString(Plugin->Descriptor.FriendlyName)), &Title) != EAppReturnType::Yes)
|
|
{
|
|
Plugin->bEnabled = false;
|
|
AllEnabledPlugins.Remove(Plugin->Name);
|
|
UE_LOG(LogPluginManager, Log, TEXT("Disabled plugin '%s' due to incompatibility"), *Plugin->FileName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPluginManager, Log, TEXT("Enabled plugin '%s' despite being built for CL %d"), *Plugin->FileName, Plugin->Descriptor.CompatibleChangelist);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHasProjectFile)
|
|
{
|
|
// Take a copy of the Project's plugins as we may remove some
|
|
TArray<FPluginReferenceDescriptor> PluginsCopy = Project->Plugins;
|
|
for(const FPluginReferenceDescriptor& Plugin: PluginsCopy)
|
|
{
|
|
bool bShouldBeEnabled = Plugin.bEnabled && Plugin.IsEnabledForPlatform(FPlatformMisc::GetUBTPlatform()) && Plugin.IsEnabledForTarget(FPlatformMisc::GetUBTTarget());
|
|
if (bShouldBeEnabled && !FindPluginInstance(Plugin.Name).IsValid() && !Plugin.bOptional
|
|
#if IS_PROGRAM
|
|
&& AllEnabledPlugins.Contains(Plugin.Name) // skip if this is a program and the plugin is not enabled
|
|
#endif
|
|
)
|
|
{
|
|
if (FApp::IsUnattended())
|
|
{
|
|
UE_LOG(LogPluginManager, Error, TEXT("This project requires the '%s' plugin. Install it and try again, or remove it from the project's required plugin list."), *Plugin.Name);
|
|
return false;
|
|
}
|
|
|
|
FText Caption(LOCTEXT("PluginMissingCaption", "Plugin missing"));
|
|
if(Plugin.MarketplaceURL.Len() > 0)
|
|
{
|
|
if(FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("PluginMissingError", "This project requires the {0} plugin.\n\nWould you like to download it from the the Marketplace?"), FText::FromString(Plugin.Name)), &Caption) == EAppReturnType::Yes)
|
|
{
|
|
FString Error;
|
|
FPlatformProcess::LaunchURL(*Plugin.MarketplaceURL, nullptr, &Error);
|
|
if(Error.Len() > 0) FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Error));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FString Description = (Plugin.Description.Len() > 0) ? FString::Printf(TEXT("\n\n%s"), *Plugin.Description) : FString();
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("PluginRequiredError", "This project requires the {0} plugin. {1}"), FText::FromString(Plugin.Name), FText::FromString(Description)), &Caption);
|
|
|
|
if (FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("PluginMissingDisable", "Would you like to disable {0}? You will no longer be able to open any assets created using it."), FText::FromString(Plugin.Name)), &Caption) == EAppReturnType::No)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FText FailReason;
|
|
if (!IProjectManager::Get().SetPluginEnabled(*Plugin.Name, false, FailReason))
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, FailReason);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we made it here, we have all the required plugins
|
|
bHaveAllRequiredPlugins = true;
|
|
|
|
for(const TPair<FString, TSharedRef<FPlugin>>& PluginPair: AllPlugins)
|
|
{
|
|
const TSharedRef<FPlugin>& Plugin = PluginPair.Value;
|
|
if (Plugin->bEnabled)
|
|
{
|
|
// Add the plugin binaries directory
|
|
const FString PluginBinariesPath = FPaths::Combine(*FPaths::GetPath(Plugin->FileName), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory());
|
|
FModuleManager::Get().AddBinariesDirectory(*PluginBinariesPath, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject);
|
|
|
|
#if !IS_MONOLITHIC
|
|
// Only check this when in a non-monolithic build where modules could be in separate binaries
|
|
if (Project != NULL && Project->Modules.Num() == 0)
|
|
{
|
|
// Content only project - check whether any plugins are incompatible and offer to disable instead of trying to build them later
|
|
TArray<FString> IncompatibleFiles;
|
|
if (!FModuleDescriptor::CheckModuleCompatibility(Plugin->Descriptor.Modules, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject, IncompatibleFiles))
|
|
{
|
|
// Ask whether to disable plugin if incompatible
|
|
FText Caption(LOCTEXT("IncompatiblePluginCaption", "Plugin missing or incompatible"));
|
|
if (FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(LOCTEXT("IncompatiblePluginText", "Missing or incompatible modules in {0} plugin - would you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(Plugin->Name)), &Caption) == EAppReturnType::No)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FText FailReason;
|
|
if (!IProjectManager::Get().SetPluginEnabled(*Plugin->Name, false, FailReason))
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, FailReason);
|
|
}
|
|
}
|
|
}
|
|
#endif //!IS_MONOLITHIC
|
|
|
|
// Build the list of content folders
|
|
if (Plugin->Descriptor.bCanContainContent)
|
|
{
|
|
if (FConfigFile* EngineConfigFile = GConfig->Find(GEngineIni, false))
|
|
{
|
|
if (FConfigSection* CoreSystemSection = EngineConfigFile->Find(TEXT("Core.System")))
|
|
{
|
|
CoreSystemSection->AddUnique("Paths", Plugin->GetContentDir());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load <PluginName>.ini config file if it exists
|
|
FString PluginConfigDir = FPaths::GetPath(Plugin->FileName) / TEXT("Config/");
|
|
FString EngineConfigDir = FPaths::EngineConfigDir();
|
|
FString SourceConfigDir = FPaths::SourceConfigDir();
|
|
|
|
// Load Engine plugins out of BasePluginName.ini and the engine directory, game plugins out of DefaultPluginName.ini
|
|
if (Plugin->LoadedFrom == EPluginLoadedFrom::Engine)
|
|
{
|
|
EngineConfigDir = PluginConfigDir;
|
|
}
|
|
else
|
|
{
|
|
SourceConfigDir = PluginConfigDir;
|
|
}
|
|
|
|
FString PluginConfigFilename = FString::Printf(TEXT("%s%s/%s.ini"), *FPaths::GeneratedConfigDir(), ANSI_TO_TCHAR(FPlatformProperties::PlatformName()), *Plugin->Name);
|
|
FConfigFile& PluginConfig = GConfig->Add(PluginConfigFilename, FConfigFile());
|
|
|
|
// This will write out an ini to PluginConfigFilename
|
|
if (!FConfigCacheIni::LoadExternalIniFile(PluginConfig, *Plugin->Name, *EngineConfigDir, *SourceConfigDir, true, nullptr, false, true))
|
|
{
|
|
// Nothing to add, remove from map
|
|
GConfig->Remove(PluginConfigFilename);
|
|
}
|
|
|
|
if (!GIsEditor)
|
|
{
|
|
// override config cache entries with plugin configs (Engine.ini, Game.ini, etc in <PluginDir>\Config\)
|
|
TArray<FString> PluginConfigs;
|
|
IFileManager::Get().FindFiles(PluginConfigs, *PluginConfigDir, TEXT("ini"));
|
|
for (const FString& ConfigFile : PluginConfigs)
|
|
{
|
|
FString PlaformName = FPlatformProperties::PlatformName();
|
|
PluginConfigFilename = FString::Printf(TEXT("%s%s/%s.ini"), *FPaths::GeneratedConfigDir(), *PlaformName, *FPaths::GetBaseFilename(ConfigFile));
|
|
FConfigFile* FoundConfig = GConfig->Find(PluginConfigFilename, false);
|
|
if (FoundConfig != nullptr)
|
|
{
|
|
FString PluginConfigContent;
|
|
if (FFileHelper::LoadFileToString(PluginConfigContent, *FPaths::Combine(PluginConfigDir, ConfigFile)))
|
|
{
|
|
FoundConfig->CombineFromBuffer(PluginConfigContent);
|
|
// if plugin config overrides are applied then don't save
|
|
FoundConfig->NoSave = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Mount all the plugin content folders and pak files
|
|
TArray<FString> FoundPaks;
|
|
FPakFileSearchVisitor PakVisitor(FoundPaks);
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
for(TSharedRef<IPlugin> Plugin: GetEnabledPlugins())
|
|
{
|
|
if (Plugin->CanContainContent() && ensure(RegisterMountPointDelegate.IsBound()))
|
|
{
|
|
FString ContentDir = Plugin->GetContentDir();
|
|
RegisterMountPointDelegate.Execute(Plugin->GetMountedAssetPath(), ContentDir);
|
|
|
|
// Pak files are loaded from <PluginName>/Content/Paks/<PlatformName>
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
FoundPaks.Reset();
|
|
PlatformFile.IterateDirectoryRecursively(*(ContentDir / TEXT("Paks") / FPlatformProperties::PlatformName()), PakVisitor);
|
|
for (const FString& PakPath : FoundPaks)
|
|
{
|
|
if (FCoreDelegates::OnMountPak.IsBound())
|
|
{
|
|
FCoreDelegates::OnMountPak.Execute(PakPath, 0, nullptr);
|
|
PluginsWithPakFile.AddUnique(Plugin);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPluginManager, Warning, TEXT("PAK file (%s) could not be mounted because OnMountPak is not bound"), *PakPath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bHaveAllRequiredPlugins;
|
|
}
|
|
|
|
TSharedPtr<FPlugin> FPluginManager::FindPluginInstance(const FString& Name)
|
|
{
|
|
const TSharedRef<FPlugin>* Instance = AllPlugins.Find(Name);
|
|
if (Instance == nullptr)
|
|
{
|
|
return TSharedPtr<FPlugin>();
|
|
}
|
|
else
|
|
{
|
|
return TSharedPtr<FPlugin>(*Instance);
|
|
}
|
|
}
|
|
|
|
|
|
static bool TryLoadModulesForPlugin( const FPlugin& Plugin, const ELoadingPhase::Type LoadingPhase )
|
|
{
|
|
TMap<FName, EModuleLoadResult> ModuleLoadFailures;
|
|
FModuleDescriptor::LoadModulesForPhase(LoadingPhase, Plugin.Descriptor.Modules, ModuleLoadFailures);
|
|
|
|
FText FailureMessage;
|
|
for( auto FailureIt( ModuleLoadFailures.CreateConstIterator() ); FailureIt; ++FailureIt )
|
|
{
|
|
const FName ModuleNameThatFailedToLoad = FailureIt.Key();
|
|
const EModuleLoadResult FailureReason = FailureIt.Value();
|
|
|
|
if( FailureReason != EModuleLoadResult::Success )
|
|
{
|
|
const FText PluginNameText = FText::FromString(Plugin.Name);
|
|
const FText TextModuleName = FText::FromName(FailureIt.Key());
|
|
|
|
if ( FailureReason == EModuleLoadResult::FileNotFound )
|
|
{
|
|
FailureMessage = FText::Format( LOCTEXT("PluginModuleNotFound", "Plugin '{0}' failed to load because module '{1}' could not be found. Please ensure the plugin is properly installed, otherwise consider disabling the plugin for this project."), PluginNameText, TextModuleName );
|
|
}
|
|
else if ( FailureReason == EModuleLoadResult::FileIncompatible )
|
|
{
|
|
FailureMessage = FText::Format( LOCTEXT("PluginModuleIncompatible", "Plugin '{0}' failed to load because module '{1}' does not appear to be compatible with the current version of the engine. The plugin may need to be recompiled."), PluginNameText, TextModuleName );
|
|
}
|
|
else if ( FailureReason == EModuleLoadResult::CouldNotBeLoadedByOS )
|
|
{
|
|
FailureMessage = FText::Format( LOCTEXT("PluginModuleCouldntBeLoaded", "Plugin '{0}' failed to load because module '{1}' could not be loaded. There may be an operating system error or the module may not be properly set up."), PluginNameText, TextModuleName );
|
|
}
|
|
else if ( FailureReason == EModuleLoadResult::FailedToInitialize )
|
|
{
|
|
FailureMessage = FText::Format( LOCTEXT("PluginModuleFailedToInitialize", "Plugin '{0}' failed to load because module '{1}' could not be initialized successfully after it was loaded."), PluginNameText, TextModuleName );
|
|
}
|
|
else
|
|
{
|
|
ensure(0); // If this goes off, the error handling code should be updated for the new enum values!
|
|
FailureMessage = FText::Format( LOCTEXT("PluginGenericLoadFailure", "Plugin '{0}' failed to load because module '{1}' could not be loaded for an unspecified reason. This plugin's functionality will not be available. Please report this error."), PluginNameText, TextModuleName );
|
|
}
|
|
|
|
// Don't need to display more than one module load error per plugin that failed to load
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !FailureMessage.IsEmpty() )
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, FailureMessage);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FPluginManager::LoadModulesForEnabledPlugins( const ELoadingPhase::Type LoadingPhase )
|
|
{
|
|
// Figure out which plugins are enabled
|
|
if(!ConfigureEnabledPlugins())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FScopedSlowTask SlowTask(AllPlugins.Num());
|
|
|
|
// Load plugins!
|
|
for( const TPair<FString, TSharedRef< FPlugin >> PluginPair : AllPlugins )
|
|
{
|
|
const TSharedRef<FPlugin> &Plugin = PluginPair.Value;
|
|
SlowTask.EnterProgressFrame(1);
|
|
|
|
if ( Plugin->bEnabled )
|
|
{
|
|
if (!TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FPluginManager::GetLocalizationPathsForEnabledPlugins( TArray<FString>& OutLocResPaths )
|
|
{
|
|
// Figure out which plugins are enabled
|
|
if (!ConfigureEnabledPlugins())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Gather the paths from all plugins that have localization targets that are loaded based on the current runtime environment
|
|
for (const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
|
|
{
|
|
const TSharedRef<FPlugin>& Plugin = PluginPair.Value;
|
|
if (!Plugin->bEnabled || Plugin->GetDescriptor().LocalizationTargets.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString PluginLocDir = Plugin->GetContentDir() / TEXT("Localization");
|
|
for (const FLocalizationTargetDescriptor& LocTargetDesc : Plugin->GetDescriptor().LocalizationTargets)
|
|
{
|
|
if (LocTargetDesc.ShouldLoadLocalizationTarget())
|
|
{
|
|
OutLocResPaths.Add(PluginLocDir / LocTargetDesc.Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPluginManager::SetRegisterMountPointDelegate( const FRegisterMountPointDelegate& Delegate )
|
|
{
|
|
RegisterMountPointDelegate = Delegate;
|
|
}
|
|
|
|
bool FPluginManager::AreRequiredPluginsAvailable()
|
|
{
|
|
return ConfigureEnabledPlugins();
|
|
}
|
|
|
|
bool FPluginManager::CheckModuleCompatibility(TArray<FString>& OutIncompatibleModules)
|
|
{
|
|
if(!ConfigureEnabledPlugins())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bResult = true;
|
|
for(const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
|
|
{
|
|
const TSharedRef< FPlugin > &Plugin = PluginPair.Value;
|
|
if (Plugin->bEnabled && !FModuleDescriptor::CheckModuleCompatibility(Plugin->Descriptor.Modules, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject, OutIncompatibleModules))
|
|
{
|
|
bResult = false;
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
IPluginManager& IPluginManager::Get()
|
|
{
|
|
// Single instance of manager, allocated on demand and destroyed on program exit.
|
|
static FPluginManager* PluginManager = NULL;
|
|
if( PluginManager == NULL )
|
|
{
|
|
PluginManager = new FPluginManager();
|
|
}
|
|
return *PluginManager;
|
|
}
|
|
|
|
TSharedPtr<IPlugin> FPluginManager::FindPlugin(const FString& Name)
|
|
{
|
|
const TSharedRef<FPlugin>* Instance = AllPlugins.Find(Name);
|
|
if (Instance == nullptr)
|
|
{
|
|
return TSharedPtr<IPlugin>();
|
|
}
|
|
else
|
|
{
|
|
return TSharedPtr<IPlugin>(*Instance);
|
|
}
|
|
}
|
|
|
|
TArray<TSharedRef<IPlugin>> FPluginManager::GetEnabledPlugins()
|
|
{
|
|
TArray<TSharedRef<IPlugin>> Plugins;
|
|
for(TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
|
|
{
|
|
TSharedRef<FPlugin>& PossiblePlugin = PluginPair.Value;
|
|
if(PossiblePlugin->bEnabled)
|
|
{
|
|
Plugins.Add(PossiblePlugin);
|
|
}
|
|
}
|
|
return Plugins;
|
|
}
|
|
|
|
TArray<TSharedRef<IPlugin>> FPluginManager::GetDiscoveredPlugins()
|
|
{
|
|
TArray<TSharedRef<IPlugin>> Plugins;
|
|
for (TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
|
|
{
|
|
Plugins.Add(PluginPair.Value);
|
|
}
|
|
return Plugins;
|
|
}
|
|
|
|
TArray< FPluginStatus > FPluginManager::QueryStatusForAllPlugins() const
|
|
{
|
|
TArray< FPluginStatus > PluginStatuses;
|
|
|
|
for( const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins )
|
|
{
|
|
const TSharedRef< FPlugin >& Plugin = PluginPair.Value;
|
|
|
|
FPluginStatus PluginStatus;
|
|
PluginStatus.Name = Plugin->Name;
|
|
PluginStatus.PluginDirectory = FPaths::GetPath(Plugin->FileName);
|
|
PluginStatus.bIsEnabled = Plugin->bEnabled;
|
|
PluginStatus.Descriptor = Plugin->Descriptor;
|
|
PluginStatus.LoadedFrom = Plugin->LoadedFrom;
|
|
|
|
PluginStatuses.Add( PluginStatus );
|
|
}
|
|
|
|
return PluginStatuses;
|
|
}
|
|
|
|
void FPluginManager::AddPluginSearchPath(const FString& ExtraDiscoveryPath, bool bRefresh)
|
|
{
|
|
PluginDiscoveryPaths.Add(ExtraDiscoveryPath);
|
|
if (bRefresh)
|
|
{
|
|
RefreshPluginsList();
|
|
}
|
|
}
|
|
|
|
TArray<TSharedRef<IPlugin>> FPluginManager::GetPluginsWithPakFile() const
|
|
{
|
|
return PluginsWithPakFile;
|
|
}
|
|
|
|
IPluginManager::FNewPluginMountedEvent& FPluginManager::OnNewPluginMounted()
|
|
{
|
|
return NewPluginMountedEvent;
|
|
}
|
|
|
|
void FPluginManager::MountNewlyCreatedPlugin(const FString& PluginName)
|
|
{
|
|
for(TMap<FString, TSharedRef<FPlugin>>::TIterator Iter(AllPlugins); Iter; ++Iter)
|
|
{
|
|
const TSharedRef<FPlugin>& Plugin = Iter.Value();
|
|
if (Plugin->Name == PluginName)
|
|
{
|
|
// Mark the plugin as enabled
|
|
Plugin->bEnabled = true;
|
|
|
|
// Mount the plugin content directory
|
|
if (Plugin->CanContainContent() && ensure(RegisterMountPointDelegate.IsBound()))
|
|
{
|
|
FString ContentDir = Plugin->GetContentDir();
|
|
RegisterMountPointDelegate.Execute(Plugin->GetMountedAssetPath(), ContentDir);
|
|
|
|
// Register this plugin's path with the list of content directories that the editor will search
|
|
if (FConfigFile* EngineConfigFile = GConfig->Find(GEngineIni, false))
|
|
{
|
|
if (FConfigSection* CoreSystemSection = EngineConfigFile->Find(TEXT("Core.System")))
|
|
{
|
|
CoreSystemSection->AddUnique("Paths", Plugin->GetContentDir());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If it's a code module, also load the modules for it
|
|
if (Plugin->Descriptor.Modules.Num() > 0)
|
|
{
|
|
// Add the plugin binaries directory
|
|
const FString PluginBinariesPath = FPaths::Combine(*FPaths::GetPath(Plugin->FileName), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory());
|
|
FModuleManager::Get().AddBinariesDirectory(*PluginBinariesPath, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject);
|
|
|
|
// Load all the plugin modules
|
|
for (ELoadingPhase::Type LoadingPhase = (ELoadingPhase::Type)0; LoadingPhase < ELoadingPhase::Max; LoadingPhase = (ELoadingPhase::Type)(LoadingPhase + 1))
|
|
{
|
|
if (LoadingPhase != ELoadingPhase::None)
|
|
{
|
|
TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify any listeners that a new plugin has been mounted
|
|
if (NewPluginMountedEvent.IsBound())
|
|
{
|
|
NewPluginMountedEvent.Broadcast(*Plugin);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|