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 #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3229011 on 2016/12/09 by Steve.Robb Licensee version updated in FWorldTileInfo::Read(). https://udn.unrealengine.com/questions/325874/fworldtileinfo-not-passing-fileversionlicenseeue4.html Change 3230493 on 2016/12/12 by Robert.Manuszewski Adding a check against assembling the reference token stream while streaming without locking GC. Change 3230515 on 2016/12/12 by Steve.Robb GetStaticEnum and GetStaticStruct removed. Various generated code tidy-ups. Change 3230522 on 2016/12/12 by Steve.Robb UHT no longer complains about bases with different prefixes. References to obsolete DependsOn removed. Change 3230528 on 2016/12/12 by Steve.Robb ReferenceChainSearch tidyups. Change 3234235 on 2016/12/14 by Robert.Manuszewski PR #2695: fix comments (Contributed by wyhily2010) Change 3234237 on 2016/12/14 by Robert.Manuszewski PR #2614: [GenericPlatformFile] New Function, GetTimeStampLocal, returns file time stamp in local time instead of UTC Rama (Contributed by EverNewJoy) Change 3236214 on 2016/12/15 by Robert.Manuszewski PR# 1988 : Allow absolute path in -UserDir=<Path> argument (contributed by bozaro) Change 3236582 on 2016/12/15 by Robert.Manuszewski Allow commandline use in shipping builds #jira UE-24613 Change 3236591 on 2016/12/15 by Robert.Manuszewski Removed unnecessary console variable logspam #jira UE-24614 Change 3236737 on 2016/12/15 by Steve.Robb Fixes to non-contiguous enums in OSS. Change 3239686 on 2016/12/19 by Chris.Wood Fixed CompressionHelper method UE4CompressFileGZIP() that leaked a file handle when a compression error occurred (CRP v1.2.12) [UE-39910] - CrashReportProcess leaks file handles and doesn't cleanup folders after compression fails during output to S3 Change 3240687 on 2016/12/20 by Chris.Wood Improved CrashReportProcess retry logic to avoid stuck threads when CRW fails to add crashes (CRP 1.2.13) [UE-39941] - Improve CrashReportProcess retry logic when CR website returns failed response to AddCrash Request Change 3246347 on 2017/01/04 by Steve.Robb Readability, debuggability and standards improvements. Change 3249122 on 2017/01/06 by Steve.Robb Generic FPaths::Combine, allowing a mix of string argument types and unlimited arity. Change 3249580 on 2017/01/06 by Steve.Robb Fix for TArray::HeapSort when array contains pointers. See: https://answers.unrealengine.com/questions/545533/bug-heapsort-with-tarray-of-pointers-fails-to-comp.html Change 3250593 on 2017/01/09 by Robert.Manuszewski PR #3046: UE-39578: Added none to invalid filenames (Contributed by projectgheist) Change 3250596 on 2017/01/09 by Robert.Manuszewski PR #3094: Fixing typo in comments for LODColoration in BaseEngine.ini - UE-40196 (Contributed by sanjay-nambiar) Change 3250599 on 2017/01/09 by Robert.Manuszewski PR #3096: Fixed Log message in ExclusiveLoadPackageTimeTracker : UE-37583 (Contributed by sanjay-nambiar) Change 3250863 on 2017/01/09 by Steve.Robb Build configuration option to force the use of the Debug version of UnrealHeaderTool. Change 3250994 on 2017/01/09 by Ben.Zeigler Remove bad or redundant ini redirects. These did not work with the old system but were silently ignored, my new system throws warnings about them Change 3251000 on 2017/01/09 by Ben.Zeigler #jira UE-39599 Add FCoreRedirects which replaces and unifies the redirect systems in LinkerLoad, K2Node, Enum, and TaggedProperty. This fixes various bugs and makes things uniform. It will parse the previous ini files, or load out of a [CoreRedirects] section in any loaded ini file The old redirect system can be re-enabled by setting USE_CORE_REDIRECTS to 0 in CoreRedirects.h. This will be removed eventually Some refactors to pass in information needed by the new system that the old system didn't need Add LoadTimeVerbose stats for processing redirects and enable that group during -LoadTimeFile Change 3253580 on 2017/01/11 by Graeme.Thornton Added some validation of the class index in exportmap entries #jira UE-37873 Change 3253777 on 2017/01/11 by Graeme.Thornton Increase SerialSize and SerialOffset in FObjectExport to 64bits, to handle super large files #jira UE-39946 Change 3257750 on 2017/01/13 by Ben.Zeigler Fix issue where incorrectly set up animation node redirects (were ActiveClassRedirects, should have been ActiveStructRedirects) didn't work in the new redirect system because it validated more. Added backward compatibilty code and fixed some conflicts in the ini. Change 3261176 on 2017/01/17 by Ben.Zeigler #jira UE-40746 Fix redundant ini redirect #jira UE-40725 Fix section of Match3 defaultengine.ini that appears to have been accidentally duplicated from baseengine.ini several years ago Change 3261915 on 2017/01/18 by Steve.Robb Fixes to localized printf formats. Change 3262142 on 2017/01/18 by Ben.Zeigler Remove runtime code for old ActiveClassRedirects and related systems. It was already disabled and the old ini format is still parsed and converted to FCoreRedirects at runtime so there should be no functionality change. Merged the deprecated tagged property and enum redirect ini parsing into LinkerLoad, and remove the RemapImports step entirely as it's part of FixupImportMap. Change 3263596 on 2017/01/19 by Gil.Gribb UE4 - Fixed many bugs with the event driven loader and allowed it to work at boot time. Change 3263597 on 2017/01/19 by Gil.Gribb UE4 - Allowed UnrealPak to do a better job with EDL pak files when the order provided is old or from the cooker. Several minor tweaks to low level async IO stuff in support of switch experiments. Change 3263922 on 2017/01/19 by Gil.Gribb UE4 - Fixed a bug with nativized blueprints that was introduced with the boot time EDL changes. Change 3264131 on 2017/01/19 by Robert.Manuszewski Simple app to test hard to repro bugs Change 3264849 on 2017/01/19 by Ben.Zeigler Change FParse::Value to treat ) like , for parsing to handle config parsing struct format. This fixes cases where lines end with bool or FName variables that aren't written out quoted: +ClassRedirects=(OldName="LandscapeProxy",NewName="LandscapeStreamingProxy",InstanceOnly=True) Change 3265232 on 2017/01/19 by Ben.Zeigler #jira UE-39599 Finish class redirect refactor by cleaning up BaseEngine.ini Move plugin-specific redirects to new plugin ini files Move all redirects from BaseEngine.ini prior to 4.11 to native registration in FCoreRedirects. Needed to split up functions to avoid long compile times Move all redirects after 4.11 to new ini format Some related blueprint fixup code changes, these weren't cooperating well with some ini redirects Change 3265490 on 2017/01/20 by Steve.Robb Prevent engine reinstancing on hot reload. #jira UE-40765 Change 3265593 on 2017/01/20 by Gil.Gribb UE4 - Stored a copy of the callback in async read request so that we don't need to worry about lifetime so we can capture variables as needed. Also fixed race in audio streaming. Change 3266003 on 2017/01/20 by Gil.Gribb UE4 - Fixed bug which would cause a fatal error when cooking subobjects that were pending kill. Change 3267433 on 2017/01/22 by Gil.Gribb UE4 - Fixed a bug with EDL at boot time which caused a fatal error with unfired imports. Change 3267677 on 2017/01/23 by Steve.Robb Fix for whitespace before UCLASS() causing compile errors. #jira UE-24110 Change 3267685 on 2017/01/23 by Steve.Robb First pass of fixes to printf-style calls to only use TCHAR[] specifiers. Change 3267746 on 2017/01/23 by Steven.Hutton Resolve offline work Changes to repositories to support better handling of db connections. Change 3267865 on 2017/01/23 by Steve.Robb Clarification of TArray::FindLastByPredicate() and FString::FindLastCharByPredicate(). #fyi nick.darnell Change 3268075 on 2017/01/23 by Gil.Gribb UE4 - Fixed another bug with RF_PendingKill subobjects and the new loader. Change 3268447 on 2017/01/23 by Gil.Gribb Fortnite - Removed calls to ::StaticClass() before main starts; this is not allowed. Change 3269491 on 2017/01/24 by Gil.Gribb UE4 - Cancelling async loading with the EDL loader now prints a warning and does a flush instead. Change 3269492 on 2017/01/24 by Gil.Gribb UE4 - Suppressed a few EDL cook wanrings. Change 3270085 on 2017/01/24 by Gil.Gribb UE4 - Remove pak highwater spam. Change 3270089 on 2017/01/24 by Gil.Gribb UE4 - fix random bug with memory counting and some vertex buffer Change 3271246 on 2017/01/25 by Chris.Wood Fixed CrashReportProcess pipeline for Mac and Linux crashes lacking machine Ids (CRP v1.2.14) [UE-40605] - Machine ID is not being shown on the crashreporter website Change 3271827 on 2017/01/25 by Steve.Robb C4946 warning disabled in third party headers (triggers in Clang/LLVM). Change 3271874 on 2017/01/25 by Steve.Robb Fix for missing error check after header preparsing. Change 3271911 on 2017/01/25 by Steve.Robb ObjectMacros.h now automatically included by generated headers. #fyi jamie.dale Change 3273125 on 2017/01/26 by Steve.Robb Check to ensure that a .generated.h header is included by headers which have exported types, to avoid crazy compiler errors. #fyi james.golding Change 3273209 on 2017/01/26 by Steve.Robb UnrealCodeAnalyzer compilation fixes. Change 3274917 on 2017/01/27 by Steve.Robb GC disabled when recompiling child BPs, as is already the case for the parent (CL# 2731120). Now-unused field removed. Change 3279091 on 2017/01/31 by Ben.Marsh UBT: Remove code paths which assume relative paths based on a particular CWD. Use the absolute paths stored in UnrealBuildTool.RootDirectory/UnrealBuildTool.EngineDirectory instead. Change 3279195 on 2017/01/31 by Gil.Gribb Turned EDL on for orion Change 3279493 on 2017/01/31 by Ben.Zeigler #jira UE-41341 Redo redirector fixups that got undone in merge from Main Change 3280284 on 2017/01/31 by Ben.Zeigler #jira UE-41357 Fix typo in vehicle redirect. Also fix base crash when converting old content with nodes that don't exist. Fix issues with loading plugin ini files. They weren't properly "diffing" against the base/default source file so my redirect typo fix didn't propagate. Some general config system refactors on Josh's advice, and make base.ini optional if reading out of a non-standard engine directory Engine plugin ini are now BasePlugin.ini, game plugins are still DefaultPlugin.ini. Fix crash when loading old content pointing to nonexistent node type. It will still error/ensure but won't crash. Change 3280299 on 2017/01/31 by Gil.Gribb possibly fix edl at boot with orion server....though was no-repro Change 3280386 on 2017/01/31 by Ben.Zeigler Header include fixes for -nopch, fixes incremental build Change 3280557 on 2017/01/31 by Ben.Zeigler Fix Config crash. FConfigFile's copy constructor is apparently not safe and resulted in garbage memory in some cases Change 3280817 on 2017/02/01 by Steve.Robb Unused SmartCastProperty removed. Change 3280897 on 2017/02/01 by Chris.Wood Improved CRP shutdown code to abort AddCrash requests when cancel is requested (CRP v1.2.15) [UE-41338] - Fix CRP shutdown when website isn't accepting new crashes Also, improved shutdown code to try to avoid occassional exception when writing out the report index. Looks like it isn't shutting down worker threads cleanly sometimes. Added more logging to this too. Change 3280989 on 2017/02/01 by Gil.Gribb New unrealpak binaries Change 3281416 on 2017/02/01 by Michael.Trepka Updated UnrealPak binaries for Mac Change 3282457 on 2017/02/01 by Ben.Zeigler #jira UE-41425 Protect against issues with streamable manager requests recursively completing by caching the array locally. This code is safer in general in my local version so just doing a quick fix for now Change 3282619 on 2017/02/01 by Arciel.Rekman Linux: update UnrealPak. [CL 3283649 by Ben Marsh in Main branch]
801 lines
26 KiB
C++
801 lines
26 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PluginManager.h"
|
|
#include "GenericPlatform/GenericPlatformFile.h"
|
|
#include "HAL/PlatformFilemanager.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 "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)
|
|
{
|
|
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.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 (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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 )
|
|
{
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|