// Copyright Epic Games, Inc. All Rights Reserved. #include "PluginReferenceDescriptor.h" #include "Misc/FileHelper.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "JsonUtils/JsonObjectArrayUpdater.h" #include "ProjectDescriptor.h" #include "JsonExtensions.h" #define LOCTEXT_NAMESPACE "PluginDescriptor" namespace PluginReferenceDescriptor { FString GetPluginRefKey(const FPluginReferenceDescriptor& PluginRef) { return PluginRef.Name; } bool TryGetPluginRefJsonObjectKey(const FJsonObject& JsonObject, FString& OutKey) { return JsonObject.TryGetStringField(TEXT("Name"), OutKey); } void UpdatePluginRefJsonObject(const FPluginReferenceDescriptor& PluginRef, FJsonObject& JsonObject) { PluginRef.UpdateJson(JsonObject); } } FPluginReferenceDescriptor::FPluginReferenceDescriptor( const FString& InName, bool bInEnabled ) : Name(InName) , bEnabled(bInEnabled) , bOptional(false) , bHasExplicitPlatforms(false) { } bool FPluginReferenceDescriptor::IsEnabledForPlatform( const FString& Platform ) const { // If it's not enabled at all, return false if(!bEnabled) { return false; } // If there is a list of allowed platform platforms, and this isn't one of them, return false if( (bHasExplicitPlatforms || PlatformAllowList.Num() > 0) && !PlatformAllowList.Contains(Platform)) { return false; } // If this platform is denied, also return false if(PlatformDenyList.Contains(Platform)) { return false; } return true; } bool FPluginReferenceDescriptor::IsEnabledForTarget(EBuildTargetType TargetType) const { // If it's not enabled at all, return false if (!bEnabled) { return false; } // If there is a list of allowed targets, and this isn't one of them, return false if (TargetAllowList.Num() > 0 && !TargetAllowList.Contains(TargetType)) { return false; } // If this platform is denied, also return false if (TargetDenyList.Contains(TargetType)) { return false; } return true; } bool FPluginReferenceDescriptor::IsEnabledForTargetConfiguration(EBuildConfiguration Configuration) const { // If it's not enabled at all, return false if (!bEnabled) { return false; } // If there is a list of allowed target configurations, and this isn't one of them, return false if (TargetConfigurationAllowList.Num() > 0 && !TargetConfigurationAllowList.Contains(Configuration)) { return false; } // If this target configuration is denied, also return false if (TargetConfigurationDenyList.Contains(Configuration)) { return false; } return true; } bool FPluginReferenceDescriptor::IsSupportedTargetPlatform(const FString& Platform) const { if (bHasExplicitPlatforms) { return SupportedTargetPlatforms.Contains(Platform); } else { return SupportedTargetPlatforms.Num() == 0 || SupportedTargetPlatforms.Contains(Platform); } } bool FPluginReferenceDescriptor::Read(const TSharedRef& Object, FText* OutFailReason /*= nullptr*/) { PRAGMA_DISABLE_DEPRECATION_WARNINGS return Read(*Object, OutFailReason, Object); PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool FPluginReferenceDescriptor::Read(const FJsonObject& Object, FText* OutFailReason /*= nullptr*/, TSharedPtr ObjectPtr /*= nullptr*/) { #if WITH_EDITOR CachedJson = ObjectPtr; AdditionalFieldsToWrite.Reset(); #endif // WITH_EDITOR bool bSuccess = true; // Get the name if (!Object.TryGetStringField(TEXT("Name"), Name)) { if (OutFailReason) { *OutFailReason = LOCTEXT("PluginReferenceWithoutName", "Plugin references must have a 'Name' field"); } bSuccess = false; } // Get the enabled field if (!Object.TryGetBoolField(TEXT("Enabled"), bEnabled)) { if (OutFailReason) { *OutFailReason = LOCTEXT("PluginReferenceWithoutEnabled", "Plugin references must have an 'Enabled' field"); } bSuccess = false; } // Read the optional field Object.TryGetBoolField(TEXT("Optional"), bOptional); // Read the metadata for users that don't have the plugin installed Object.TryGetStringField(TEXT("Description"), Description); Object.TryGetStringField(TEXT("MarketplaceURL"), MarketplaceURL); // Get the platform lists JsonExtensions::TryGetStringArrayFieldWithDeprecatedFallback(Object, TEXT("PlatformAllowList"), TEXT("WhitelistPlatforms"), /*out*/ PlatformAllowList); JsonExtensions::TryGetStringArrayFieldWithDeprecatedFallback(Object, TEXT("PlatformDenyList"), TEXT("BlacklistPlatforms"), /*out*/ PlatformDenyList); // Get the target configuration lists JsonExtensions::TryGetEnumArrayFieldWithDeprecatedFallback(Object, TEXT("TargetConfigurationAllowList"), TEXT("WhitelistTargetConfigurations"), /*out*/ TargetConfigurationAllowList); JsonExtensions::TryGetEnumArrayFieldWithDeprecatedFallback(Object, TEXT("TargetConfigurationDenyList"), TEXT("BlacklistTargetConfigurations"), /*out*/ TargetConfigurationDenyList); // Get the target lists JsonExtensions::TryGetEnumArrayFieldWithDeprecatedFallback(Object, TEXT("TargetAllowList"), TEXT("WhitelistTargets"), /*out*/ TargetAllowList); JsonExtensions::TryGetEnumArrayFieldWithDeprecatedFallback(Object, TEXT("TargetDenyList"), TEXT("BlacklistTargets"), /*out*/ TargetDenyList); // Get the supported platform list Object.TryGetStringArrayField(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatforms); Object.TryGetBoolField(TEXT("HasExplicitPlatforms"), bHasExplicitPlatforms); int32 ReadVersion; if (Object.TryGetNumberField(TEXT("Version"), ReadVersion)) { RequestedVersion = ReadVersion; if (!bEnabled) { if (bSuccess && OutFailReason) { *OutFailReason = LOCTEXT("PluginReferenceDisabledWithVersion", "Plugin references cannot be used to disable explicit versions. Remove the 'Version' field when 'Enabled' is false."); } bSuccess = false; } } return bSuccess; } bool FPluginReferenceDescriptor::Read(const FJsonObject& Object, FText& OutFailReason, TSharedPtr ObjectPtr /*= nullptr*/) { PRAGMA_DISABLE_DEPRECATION_WARNINGS return Read(Object, &OutFailReason, ObjectPtr); PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool FPluginReferenceDescriptor::ReadArray(const FJsonObject& Object, const TCHAR* Name, TArray& OutPlugins, FText* OutFailReason /*= nullptr*/) { const TArray< TSharedPtr >* Array; if (Object.TryGetArrayField(Name, Array)) { for (const TSharedPtr &Item : *Array) { const TSharedPtr* ItemObject = nullptr; if (Item.IsValid() && Item->TryGetObject(ItemObject) && ItemObject && ItemObject->IsValid()) { FPluginReferenceDescriptor PluginRef; if (!PluginRef.Read(ItemObject->ToSharedRef(), OutFailReason)) { return false; } OutPlugins.Add(PluginRef); } } } return true; } bool FPluginReferenceDescriptor::ReadArray(const FJsonObject& Object, const TCHAR* Name, TArray& OutPlugins, FText& OutFailReason) { return ReadArray(Object, Name, OutPlugins, &OutFailReason); } void FPluginReferenceDescriptor::Write(TJsonWriter<>& Writer) const { TSharedPtr PluginRefJsonObject = MakeShared(); #if WITH_EDITOR if (CachedJson.IsValid()) { FJsonObject::Duplicate(/*Source=*/ CachedJson, /*Dest=*/ PluginRefJsonObject); } #endif //if WITH_EDITOR UpdateJson(*PluginRefJsonObject); FJsonSerializer::Serialize(PluginRefJsonObject.ToSharedRef(), Writer); } void FPluginReferenceDescriptor::UpdateJson(FJsonObject& JsonObject) const { JsonObject.SetStringField(TEXT("Name"), Name); JsonObject.SetBoolField(TEXT("Enabled"), bEnabled); if (bEnabled && bOptional) { JsonObject.SetBoolField(TEXT("Optional"), bOptional); } else { JsonObject.RemoveField(TEXT("Optional")); } if (Description.Len() > 0) { JsonObject.SetStringField(TEXT("Description"), Description); } else { JsonObject.RemoveField(TEXT("Description")); } if (MarketplaceURL.Len() > 0) { JsonObject.SetStringField(TEXT("MarketplaceURL"), MarketplaceURL); } else { JsonObject.RemoveField(TEXT("MarketplaceURL")); } if (PlatformAllowList.Num() > 0) { TArray> PlatformAllowListValues; for (const FString& Platform : PlatformAllowList) { PlatformAllowListValues.Add(MakeShareable(new FJsonValueString(Platform))); } JsonObject.SetArrayField(TEXT("PlatformAllowList"), PlatformAllowListValues); } else { JsonObject.RemoveField(TEXT("PlatformAllowList")); } if (PlatformDenyList.Num() > 0) { TArray> PlatformDenyListValues; for (const FString& Platform : PlatformDenyList) { PlatformDenyListValues.Add(MakeShareable(new FJsonValueString(Platform))); } JsonObject.SetArrayField(TEXT("PlatformDenyList"), PlatformDenyListValues); } else { JsonObject.RemoveField(TEXT("PlatformDenyList")); } if (TargetConfigurationAllowList.Num() > 0) { TArray> TargetConfigurationAllowListValues; for (EBuildConfiguration Config : TargetConfigurationAllowList) { TargetConfigurationAllowListValues.Add(MakeShareable(new FJsonValueString(LexToString(Config)))); } JsonObject.SetArrayField(TEXT("TargetConfigurationAllowList"), TargetConfigurationAllowListValues); } else { JsonObject.RemoveField(TEXT("TargetConfigurationAllowList")); } if (TargetConfigurationDenyList.Num() > 0) { TArray> TargetConfigurationDenyListValues; for (EBuildConfiguration Config : TargetConfigurationDenyList) { TargetConfigurationDenyListValues.Add(MakeShareable(new FJsonValueString(LexToString(Config)))); } JsonObject.SetArrayField(TEXT("TargetConfigurationDenyList"), TargetConfigurationDenyListValues); } else { JsonObject.RemoveField(TEXT("TargetConfigurationDenyList")); } if (TargetAllowList.Num() > 0) { TArray> TargetAllowListValues; for (EBuildTargetType Target : TargetAllowList) { TargetAllowListValues.Add(MakeShareable(new FJsonValueString(LexToString(Target)))); } JsonObject.SetArrayField(TEXT("TargetAllowList"), TargetAllowListValues); } else { JsonObject.RemoveField(TEXT("TargetAllowList")); } if (TargetDenyList.Num() > 0) { TArray> TargetDenyListValues; for (EBuildTargetType Target : TargetDenyList) { TargetDenyListValues.Add(MakeShareable(new FJsonValueString(LexToString(Target)))); } JsonObject.SetArrayField(TEXT("TargetDenyList"), TargetDenyListValues); } else { JsonObject.RemoveField(TEXT("TargetDenyList")); } if (SupportedTargetPlatforms.Num() > 0) { TArray> SupportedTargetPlatformValues; for (const FString& SupportedTargetPlatform : SupportedTargetPlatforms) { SupportedTargetPlatformValues.Add(MakeShareable(new FJsonValueString(SupportedTargetPlatform))); } JsonObject.SetArrayField(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatformValues); } else { JsonObject.RemoveField(TEXT("SupportedTargetPlatforms")); } if (bHasExplicitPlatforms) { JsonObject.SetBoolField(TEXT("HasExplicitPlatforms"), bHasExplicitPlatforms); } else { JsonObject.RemoveField(TEXT("HasExplicitPlatforms")); } if (bEnabled && RequestedVersion.IsSet()) { JsonObject.SetNumberField(TEXT("Version"), RequestedVersion.GetValue()); } else { JsonObject.RemoveField(TEXT("Version")); } // Remove deprecated fields JsonObject.RemoveField(TEXT("WhitelistPlatforms")); JsonObject.RemoveField(TEXT("BlacklistPlatforms")); JsonObject.RemoveField(TEXT("WhitelistTargetConfigurations")); JsonObject.RemoveField(TEXT("BlacklistTargetConfigurations")); JsonObject.RemoveField(TEXT("WhitelistTargets")); JsonObject.RemoveField(TEXT("BlacklistTargets")); #if WITH_EDITOR for (const auto& KVP : AdditionalFieldsToWrite) { JsonObject.SetField(KVP.Key, FJsonValue::Duplicate(KVP.Value)); } #endif //if WITH_EDITOR } void FPluginReferenceDescriptor::WriteArray(TJsonWriter<>& Writer, const TCHAR* ArrayName, const TArray& Plugins) { if (Plugins.Num() > 0) { Writer.WriteArrayStart(ArrayName); for (const FPluginReferenceDescriptor& PluginRef : Plugins) { PluginRef.Write(Writer); } Writer.WriteArrayEnd(); } } void FPluginReferenceDescriptor::UpdateArray(FJsonObject& JsonObject, const TCHAR* ArrayName, const TArray& Plugins) { typedef FJsonObjectArrayUpdater FPluginRefJsonArrayUpdater; FPluginRefJsonArrayUpdater::Execute( JsonObject, ArrayName, Plugins, FPluginRefJsonArrayUpdater::FGetElementKey::CreateStatic(PluginReferenceDescriptor::GetPluginRefKey), FPluginRefJsonArrayUpdater::FTryGetJsonObjectKey::CreateStatic(PluginReferenceDescriptor::TryGetPluginRefJsonObjectKey), FPluginRefJsonArrayUpdater::FUpdateJsonObject::CreateStatic(PluginReferenceDescriptor::UpdatePluginRefJsonObject), FPluginRefJsonArrayUpdater::FSortArray::CreateLambda([&Plugins](TArray>& NewJsonValues) { // Sort the json array to match the same order as the plugin array. Without the sort, new entries are appended at the end for (int32 StartIndex = 0; StartIndex < Plugins.Num(); ++StartIndex) { const FString PluginRefKey = PluginReferenceDescriptor::GetPluginRefKey(Plugins[StartIndex]); for (int32 Index = StartIndex; Index < NewJsonValues.Num(); ++Index) { const TSharedPtr* ExistingJsonValueAsObject; if (NewJsonValues[Index]->TryGetObject(ExistingJsonValueAsObject)) { FString ElementKey; if (PluginReferenceDescriptor::TryGetPluginRefJsonObjectKey(**ExistingJsonValueAsObject, ElementKey)) { if (ElementKey == PluginRefKey) { NewJsonValues.Swap(StartIndex, Index); break; } } } } } }) ); } #if WITH_EDITOR bool FPluginReferenceDescriptor::GetAdditionalStringField(const FString& Key, FString& OutValue) const { if (const TSharedPtr* ValuePtr = AdditionalFieldsToWrite.Find(Key)) { const TSharedPtr& Value = *ValuePtr; if (Value && Value->Type == EJson::String) { OutValue = Value->AsString(); return true; } } if (CachedJson) { if (TSharedPtr Value = CachedJson->TryGetField(Key)) { if (Value->Type == EJson::String) { OutValue = Value->AsString(); return true; } } } return false; } #endif //if WITH_EDITOR #undef LOCTEXT_NAMESPACE