// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "ProjectsPrivatePCH.h" #include "EngineVersion.h" #define LOCTEXT_NAMESPACE "PluginManagerShared" void FProjectOrPlugin::LoadModules( const ELoadingPhase::Type LoadingPhase, TMap& ModuleLoadErrors ) { const FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); for( TArray< FProjectOrPluginInfo::FModuleInfo >::TConstIterator ModuleInfoIt( ProjectOrPluginInfo.Modules.CreateConstIterator() ); ModuleInfoIt; ++ModuleInfoIt ) { const FProjectOrPluginInfo::FModuleInfo& ModuleInfo = *ModuleInfoIt; // Don't need to do anything if this module is already loaded if( !FModuleManager::Get().IsModuleLoaded( ModuleInfo.Name ) ) { if( LoadingPhase == ModuleInfo.LoadingPhase && ShouldLoadModule(ModuleInfo) ) { // @todo plugin: DLL search problems. Plugins that statically depend on other modules within this plugin may not be found? Need to test this. // NOTE: Loading this module may cause other modules to become loaded, both in the engine or game, or other modules // that are part of this project or plugin. That's totally fine. ELoadModuleFailureReason::Type FailureReason; const TSharedPtr& ModuleInterface = FModuleManager::Get().LoadModuleWithFailureReason( ModuleInfo.Name, FailureReason ); if( ModuleInterface.IsValid() ) { // Module loaded OK (or was already loaded.) } else { // The module failed to load. Note this in the ModuleLoadErrors list. ModuleLoadErrors.Add(ModuleInfo.Name, FailureReason); } } } } } bool FProjectOrPlugin::LoadFromFile( const FString& FileToLoad, FText& OutFailureReason ) { const FText FileDisplayName = FText::FromString(FileToLoad); FString FileContents; if (!FFileHelper::LoadFileToString(FileContents, *FileToLoad)) { // Failed to read project file OutFailureReason = FText::Format( LOCTEXT("FailedToLoadDescriptorFile", "Failed to open descriptor file '{0}'"), FileDisplayName ); return false; } if (FileContents.IsEmpty()) { // Empty project file OutFailureReason = FText::Format( LOCTEXT("DescriptorFileEmpty", "Descriptor file '{0}' was empty."), FileDisplayName ); return false; } // Serialize the JSON file contents FText DeserializeFailReason; if (!DeserializeFromJSON(FileContents, DeserializeFailReason)) { OutFailureReason = FText::Format( LOCTEXT("DescriptorDeserializeFailed", "'Descriptor file '{0}' could not be loaded. {1}"), FileDisplayName, DeserializeFailReason ); return false; } FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); ProjectOrPluginInfo.Name = FPaths::GetBaseFilename(FileToLoad); ProjectOrPluginInfo.LoadedFromFile = FileToLoad; return true; } bool FProjectOrPlugin::DeserializeFromJSON( const FString& JSONInput, FText& OutFailReason ) { FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); TSharedPtr< FJsonObject > FileObjectPtr; TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(JSONInput); if ( !FJsonSerializer::Deserialize(Reader, FileObjectPtr) || !FileObjectPtr.IsValid() ) { OutFailReason = FText::Format( LOCTEXT("FailedToReadDescriptorFile", "Failed to read file. {1}"), FText::FromString(Reader->GetErrorMessage()) ); return false; } TSharedRef< FJsonObject > FileObject = FileObjectPtr.ToSharedRef(); bool bSuccessful = true; // FileVersion. This is not optional. if ( ReadFileVersionFromJSON(FileObject, ProjectOrPluginInfo.FileVersion) ) { if ( ProjectOrPluginInfo.FileVersion == INDEX_NONE ) { OutFailReason = LOCTEXT("InvalidProjectFileVersion", "File does not have a valid 'FileVersion' number."); bSuccessful = false; } if ( ProjectOrPluginInfo.FileVersion > VER_LATEST_PROJECT_FILE ) { OutFailReason = FText::Format( LOCTEXT("ProjectFileVersionTooLarge", "File appears to be in a newer version ({0}) of the file format that we can load (max version: {1})."), FText::FromString( FString::Printf( TEXT( "%d" ), ProjectOrPluginInfo.FileVersion ) ), FText::FromString( FString::Printf( TEXT( "%d" ), (int32)VER_LATEST_PROJECT_FILE ) ) ); bSuccessful = false; } } else { OutFailReason = LOCTEXT("NoProjectFileVersion", "File is missing a 'FileVersion' field. This is required in order to load the file."); bSuccessful = false; } if ( bSuccessful ) { ReadNumberFromJSON(FileObject, TEXT("Version"), ProjectOrPluginInfo.Version); ReadStringFromJSON(FileObject, TEXT("VersionName"), ProjectOrPluginInfo.VersionName); FString EngineVersionString; ReadStringFromJSON(FileObject, TEXT("EngineVersion"), EngineVersionString); FEngineVersion::Parse(EngineVersionString, ProjectOrPluginInfo.EngineVersion); ReadNumberFromJSON(FileObject, TEXT("PackageFileUE4Version"), ProjectOrPluginInfo.PackageFileUE4Version); ReadNumberFromJSON(FileObject, TEXT("PackageFileLicenseeUE4Version"), ProjectOrPluginInfo.PackageFileLicenseeUE4Version); ReadStringFromJSON(FileObject, TEXT("EngineAssociation"), ProjectOrPluginInfo.EngineAssociation); ReadStringFromJSON(FileObject, TEXT("FriendlyName"), ProjectOrPluginInfo.FriendlyName); ReadStringFromJSON(FileObject, TEXT("Description"), ProjectOrPluginInfo.Description); if ( !ReadStringFromJSON(FileObject, TEXT("Category"), ProjectOrPluginInfo.Category) ) { // Category used to be called CategoryPath in .uplugin files ReadStringFromJSON(FileObject, TEXT("CategoryPath"), ProjectOrPluginInfo.Category); } // Due to a difference in command line parsing between Windows and Mac, we shipped a few Mac samples containing // a category name with escaped quotes. Remove them here to make sure we can list them in the right category. if(ProjectOrPluginInfo.Category.Len() >= 2 && ProjectOrPluginInfo.Category.StartsWith(TEXT("\"")) && ProjectOrPluginInfo.Category.EndsWith(TEXT("\""))) { ProjectOrPluginInfo.Category = ProjectOrPluginInfo.Category.Mid(1, ProjectOrPluginInfo.Category.Len() - 2); } ReadStringFromJSON(FileObject, TEXT("CreatedBy"), ProjectOrPluginInfo.CreatedBy); ReadStringFromJSON(FileObject, TEXT("CreatedByURL"), ProjectOrPluginInfo.CreatedByURL); if ( ReadModulesFromJSON(FileObject, ProjectOrPluginInfo.Modules, OutFailReason) ) { ProjectOrPluginInfo.EarliestModulePhase = FProjectAndPluginManager::GetEarliestPhaseFromModules(ProjectOrPluginInfo.Modules); } else { bSuccessful = false; } } if ( bSuccessful ) { bSuccessful = PerformAdditionalDeserialization(FileObject); } return bSuccessful; } FString FProjectOrPlugin::SerializeToJSON( ) const { const FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); FString JSONOutput; TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&JSONOutput); Writer->WriteObjectStart(); Writer->WriteValue(TEXT("FileVersion"), (float)ProjectOrPluginInfo.FileVersion); Writer->WriteValue(TEXT("Version"), (float)ProjectOrPluginInfo.Version); Writer->WriteValue(TEXT("VersionName"), ProjectOrPluginInfo.VersionName); Writer->WriteValue(TEXT("EngineVersion"), ProjectOrPluginInfo.EngineVersion.ToString()); Writer->WriteValue(TEXT("PackageFileUE4Version"), (float)ProjectOrPluginInfo.PackageFileUE4Version); Writer->WriteValue(TEXT("PackageFileLicenseeUE4Version"), (float)ProjectOrPluginInfo.PackageFileLicenseeUE4Version); Writer->WriteValue(TEXT("EngineAssociation"), ProjectOrPluginInfo.EngineAssociation); Writer->WriteValue(TEXT("FriendlyName"), ProjectOrPluginInfo.FriendlyName); Writer->WriteValue(TEXT("Description"), ProjectOrPluginInfo.Description); Writer->WriteValue(TEXT("Category"), ProjectOrPluginInfo.Category); Writer->WriteValue(TEXT("CreatedBy"), ProjectOrPluginInfo.CreatedBy); Writer->WriteValue(TEXT("CreatedByURL"), ProjectOrPluginInfo.CreatedByURL); if ( ProjectOrPluginInfo.Modules.Num() > 0 ) { Writer->WriteArrayStart(TEXT("Modules")); for ( auto ModuleIt = ProjectOrPluginInfo.Modules.CreateConstIterator(); ModuleIt; ++ModuleIt ) { const FProjectOrPluginInfo::FModuleInfo& Module = *ModuleIt; Writer->WriteObjectStart(); Writer->WriteValue(TEXT("Name"), Module.Name.ToString()); Writer->WriteValue(TEXT("Type"), FString(FProjectOrPluginInfo::EModuleType::ToString(Module.Type))); Writer->WriteValue(TEXT("LoadingPhase"), FString(ELoadingPhase::ToString(Module.LoadingPhase))); if (Module.WhitelistPlatforms.Num() > 0) { Writer->WriteArrayStart(TEXT("WhitelistPlatforms")); for ( auto PlatformIt = Module.WhitelistPlatforms.CreateConstIterator(); PlatformIt; ++PlatformIt ) { Writer->WriteValue(*PlatformIt); } Writer->WriteArrayEnd(); } if (Module.BlacklistPlatforms.Num() > 0) { Writer->WriteArrayStart(TEXT("BlacklistPlatforms")); for ( auto PlatformIt = Module.BlacklistPlatforms.CreateConstIterator(); PlatformIt; ++PlatformIt ) { Writer->WriteValue(*PlatformIt); } Writer->WriteArrayEnd(); } Writer->WriteObjectEnd(); } Writer->WriteArrayEnd(); } PerformAdditionalSerialization(Writer); Writer->WriteObjectEnd(); Writer->Close(); return JSONOutput; } bool FProjectOrPlugin::RequiresUpdate( ) const { const FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); return (ProjectOrPluginInfo.FileVersion < VER_LATEST_PROJECT_FILE); } bool FProjectOrPlugin::AreModulesUpToDate( ) const { const FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); for (TArray< FProjectOrPluginInfo::FModuleInfo >::TConstIterator Iter(ProjectOrPluginInfo.Modules); Iter; ++Iter) { const FProjectOrPluginInfo::FModuleInfo &ModuleInfo = *Iter; if (ShouldBuildModule(ModuleInfo) && !FModuleManager::Get().IsModuleUpToDate(ModuleInfo.Name)) { return false; } } return true; } void FProjectOrPlugin::UpdateVersionToCurrent( const FString &EngineIdentifier ) { FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); ProjectOrPluginInfo.FileVersion = VER_LATEST_PROJECT_FILE; ProjectOrPluginInfo.EngineVersion = GEngineVersion; ProjectOrPluginInfo.PackageFileUE4Version = GPackageFileUE4Version; ProjectOrPluginInfo.PackageFileLicenseeUE4Version = GPackageFileLicenseeUE4Version; ProjectOrPluginInfo.EngineAssociation = EngineIdentifier; } void FProjectOrPlugin::ReplaceModulesInProject(const TArray* StartupModuleNames) { if ( StartupModuleNames ) { FProjectOrPluginInfo& ProjectOrPluginInfo = GetProjectOrPluginInfo(); ProjectOrPluginInfo.Modules.Empty(); for ( auto ModuleIt = StartupModuleNames->CreateConstIterator(); ModuleIt; ++ModuleIt ) { FProjectOrPluginInfo::FModuleInfo ModuleInfo; ModuleInfo.Name = FName(**ModuleIt); ProjectOrPluginInfo.Modules.Add(ModuleInfo); } } } bool FProjectOrPlugin::ShouldBuildModule(const FProjectOrPluginInfo::FModuleInfo& ModuleInfo) const { // Cache the string for the current platform static FString UBTPlatform(FPlatformMisc::GetUBTPlatform()); // Check the platform is whitelisted if(ModuleInfo.WhitelistPlatforms.Num() > 0 && !ModuleInfo.WhitelistPlatforms.Contains(UBTPlatform)) { return false; } // Check the platform is not blacklisted if(ModuleInfo.BlacklistPlatforms.Num() > 0 && ModuleInfo.BlacklistPlatforms.Contains(UBTPlatform)) { return false; } // Check the module is compatible with this target. This should match UEBuildTarget.ShouldIncludePluginModule in UBT switch (ModuleInfo.Type) { case FProjectOrPluginInfo::EModuleType::Runtime: case FProjectOrPluginInfo::EModuleType::RuntimeNoCommandlet: return true; case FProjectOrPluginInfo::EModuleType::Developer: #if WITH_UNREAL_DEVELOPER_TOOLS return true; #endif break; case FProjectOrPluginInfo::EModuleType::Editor: case FProjectOrPluginInfo::EModuleType::EditorNoCommandlet: #if WITH_EDITOR return true; #endif break; case FProjectOrPluginInfo::EModuleType::Program: #if IS_PROGRAM return true; #endif break; } return false; } bool FProjectOrPlugin::ShouldLoadModule(const FProjectOrPluginInfo::FModuleInfo& ModuleInfo) const { // Check that the module is built for this configuration if(!ShouldBuildModule(ModuleInfo)) { return false; } // Check that the runtime environment allows it to be loaded switch (ModuleInfo.Type) { case FProjectOrPluginInfo::EModuleType::Runtime: #if WITH_ENGINE || WITH_PLUGIN_SUPPORT return true; #endif break; case FProjectOrPluginInfo::EModuleType::RuntimeNoCommandlet: #if WITH_ENGINE || WITH_PLUGIN_SUPPORT if(!IsRunningCommandlet()) return true; #endif break; case FProjectOrPluginInfo::EModuleType::Developer: #if WITH_UNREAL_DEVELOPER_TOOLS return true; #endif break; case FProjectOrPluginInfo::EModuleType::Editor: #if WITH_EDITOR if(GIsEditor) return true; #endif break; case FProjectOrPluginInfo::EModuleType::EditorNoCommandlet: #if WITH_EDITOR if(GIsEditor && !IsRunningCommandlet()) return true; #endif break; case FProjectOrPluginInfo::EModuleType::Program: #if WITH_PLUGIN_SUPPORT && IS_PROGRAM return true; #endif break; } return false; } bool FProjectOrPlugin::ReadNumberFromJSON(const TSharedRef< FJsonObject >& FileObject, const FString& PropertyName, int32& OutNumber ) const { int64 LargeNumber; if(ReadNumberFromJSON(FileObject, PropertyName, LargeNumber)) { OutNumber = (int32)FMath::Clamp(LargeNumber, INT_MIN, INT_MAX); return true; } return false; } bool FProjectOrPlugin::ReadNumberFromJSON(const TSharedRef< FJsonObject >& FileObject, const FString& PropertyName, uint32& OutNumber ) const { int64 LargeNumber; if(ReadNumberFromJSON(FileObject, PropertyName, LargeNumber)) { OutNumber = (uint32)FMath::Clamp(LargeNumber, 0, UINT_MAX); return true; } return false; } bool FProjectOrPlugin::ReadNumberFromJSON(const TSharedRef< FJsonObject >& FileObject, const FString& PropertyName, int64& OutNumber ) const { if ( FileObject->HasTypedField(PropertyName) ) { TSharedPtr NumberValue = FileObject->GetField(PropertyName); if ( NumberValue.IsValid() ) { OutNumber = (int64)( NumberValue->AsNumber() + 0.5 ); return true; } } // We are tolerant to number fields with quotes around them else if ( FileObject->HasTypedField(PropertyName) ) { TSharedPtr StringValue = FileObject->GetField(PropertyName); if ( StringValue.IsValid() ) { OutNumber = FCString::Atoi64( *StringValue->AsString() ); return true; } } return false; } bool FProjectOrPlugin::ReadStringFromJSON(const TSharedRef< FJsonObject >& FileObject, const FString& PropertyName, FString& OutString ) const { if ( FileObject->HasTypedField(PropertyName) ) { TSharedPtr StringValue = FileObject->GetField(PropertyName); if ( StringValue.IsValid() ) { OutString = StringValue->AsString(); return true; } } return false; } bool FProjectOrPlugin::ReadBoolFromJSON(const TSharedRef< FJsonObject >& FileObject, const FString& PropertyName, bool& OutBool ) const { if ( FileObject->HasTypedField(PropertyName) ) { TSharedPtr BoolValue = FileObject->GetField(PropertyName); if ( BoolValue.IsValid() ) { OutBool = BoolValue->AsBool(); return true; } } return false; } bool FProjectOrPlugin::ReadFileVersionFromJSON(const TSharedRef< FJsonObject >& FileObject, int32& OutVersion ) const { // Modern file version if ( ReadNumberFromJSON(FileObject, TEXT("FileVersion"), OutVersion) ) { return true; } // Back compat with old project files if ( ReadNumberFromJSON(FileObject, TEXT("ProjectFileVersion"), OutVersion) ) { return true; } // Back compat with old plugin files if ( ReadNumberFromJSON(FileObject, TEXT("PluginFileVersion"), OutVersion) ) { return true; } return false; } bool FProjectOrPlugin::ReadModulesFromJSON(const TSharedRef< FJsonObject >& FileObject, TArray& OutModules, FText& OutFailReason ) const { bool bLoadedSuccessfully = true; // This project or plugin might have some modules that we need to know about. Let's take a look. if( FileObject->HasField( TEXT( "Modules" ) ) ) { const TSharedPtr< FJsonValue >& ModulesValue = FileObject->GetField< EJson::Array >( TEXT( "Modules" ) ); const TArray< TSharedPtr< FJsonValue > >& ModulesArray = ModulesValue->AsArray(); for( auto ModuleValueIt( ModulesArray.CreateConstIterator() ); ModuleValueIt; ++ModuleValueIt ) { const TSharedPtr< FJsonObject >& ModuleObjectPtr = ( *ModuleValueIt )->AsObject(); if( ModuleObjectPtr.IsValid() ) { const TSharedRef< FJsonObject >& ModuleObject = ModuleObjectPtr.ToSharedRef(); FProjectOrPluginInfo::FModuleInfo ModuleInfo; // Module name { // All modules require a name to be set FString ModuleName; if ( ReadStringFromJSON(ModuleObject, TEXT("Name"), ModuleName) ) { ModuleInfo.Name = FName(*ModuleName); } else { OutFailReason = LOCTEXT("ModuleWithoutAName", "Found a 'Module' entry with a missing 'Name' field"); bLoadedSuccessfully = false; continue; } } // Module type { FString ModuleType; if ( ReadStringFromJSON(ModuleObject, TEXT("Type"), ModuleType) ) { // Check to see if this is a valid type bool bFoundValidType = false; for( int32 PossibleTypeIndex = 0; PossibleTypeIndex < FProjectOrPluginInfo::EModuleType::Max; ++PossibleTypeIndex ) { const FProjectOrPluginInfo::EModuleType::Type PossibleType = (FProjectOrPluginInfo::EModuleType::Type)PossibleTypeIndex; const TCHAR* PossibleTypeName = FProjectOrPluginInfo::EModuleType::ToString( PossibleType ); if( FCString::Stricmp( PossibleTypeName, *ModuleType ) == 0 ) { bFoundValidType = true; ModuleInfo.Type = PossibleType; break; } } if( !bFoundValidType ) { OutFailReason = FText::Format( LOCTEXT( "ModuleWithInvalidType", "Module entry '{0}' specified an unrecognized module Type '{1}'" ), FText::FromName(ModuleInfo.Name), FText::FromString(ModuleType) ); bLoadedSuccessfully = false; continue; } } else { OutFailReason = FText::Format( LOCTEXT( "ModuleWithoutAType", "Found Module entry '{0}' with a missing 'Type' field" ), FText::FromName(ModuleInfo.Name) ); bLoadedSuccessfully = false; break; } } // Loading phase { FString ModuleLoadingPhase; if ( ReadStringFromJSON(ModuleObject, TEXT("LoadingPhase"), ModuleLoadingPhase) ) { // Check to see if this is a valid Phase bool bFoundValidPhase = false; for( int32 PossiblePhaseIndex = 0; PossiblePhaseIndex < ELoadingPhase::Max; ++PossiblePhaseIndex ) { const ELoadingPhase::Type PossiblePhase = (ELoadingPhase::Type)PossiblePhaseIndex; const TCHAR* PossiblePhaseName = ELoadingPhase::ToString( PossiblePhase ); if( FCString::Stricmp( PossiblePhaseName, *ModuleLoadingPhase ) == 0 ) { bFoundValidPhase = true; ModuleInfo.LoadingPhase = PossiblePhase; break; } } if( !bFoundValidPhase ) { OutFailReason = FText::Format( LOCTEXT( "ModuleWithInvalidLoadingPhase", "Module entry '{0}' specified an unrecognized module LoadingPhase '{1}'" ), FText::FromName(ModuleInfo.Name), FText::FromString(ModuleLoadingPhase) ); bLoadedSuccessfully = false; continue; } } else { // No 'LoadingPhase' was specified in the file. That's totally fine, we'll use the default. } } // Whitelist platforms { if( ModuleObject->HasField( TEXT( "WhitelistPlatforms" ) ) ) { // walk over the array values const TSharedPtr< FJsonValue >& PlatformsValue = ModuleObject->GetField< EJson::Array >( TEXT( "WhitelistPlatforms" ) ); const TArray< TSharedPtr< FJsonValue > >& PlatformsArray = PlatformsValue->AsArray(); for( auto PlatformValueIt( PlatformsArray.CreateConstIterator() ); PlatformValueIt; ++PlatformValueIt ) { ModuleInfo.WhitelistPlatforms.Add((*PlatformValueIt)->AsString()); } } } // Blacklist platforms { if( ModuleObject->HasField( TEXT( "BlacklistPlatforms" ) ) ) { // walk over the array values const TSharedPtr< FJsonValue >& PlatformsValue = ModuleObject->GetField< EJson::Array >( TEXT( "BlacklistPlatforms" ) ); const TArray< TSharedPtr< FJsonValue > >& PlatformsArray = PlatformsValue->AsArray(); for( auto PlatformValueIt( PlatformsArray.CreateConstIterator() ); PlatformValueIt; ++PlatformValueIt ) { ModuleInfo.BlacklistPlatforms.Add((*PlatformValueIt)->AsString()); } } } OutModules.Add(ModuleInfo); } else { OutFailReason = LOCTEXT( "ModuleWithInvalidModulesArray", "The 'Modules' array has invalid contents and was not able to be loaded." ); bLoadedSuccessfully = false; } } } return bLoadedSuccessfully; } ELoadingPhase::Type FProjectAndPluginManager::GetEarliestPhaseFromModules(const TArray& Modules) { ELoadingPhase::Type EarliestPhase = ELoadingPhase::Default; for ( auto ModuleIt = Modules.CreateConstIterator(); ModuleIt; ++ModuleIt ) { EarliestPhase = ELoadingPhase::GetEarlierPhase(EarliestPhase, (*ModuleIt).LoadingPhase); } return EarliestPhase; } #undef LOCTEXT_NAMESPACE