// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "PluginDescriptor.h" #include "Misc/FileHelper.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "ProjectDescriptor.h" #define LOCTEXT_NAMESPACE "PluginDescriptor" /** * Version numbers for plugin descriptors. These version numbers are not generally needed; serialization from JSON attempts to be tolerant of missing/added fields. */ enum class EPluginDescriptorVersion : uint8 { Invalid = 0, Initial = 1, NameHash = 2, ProjectPluginUnification = 3, // !!!!!!!!!! IMPORTANT: Remember to also update LatestPluginDescriptorFileVersion in Plugins.cs (and Plugin system documentation) when this changes!!!!!!!!!!! // ------------------------------------------------------ // - this needs to be the last line (see note below) LatestPlusOne, Latest = LatestPlusOne - 1 }; FPluginDescriptor::FPluginDescriptor() : Version(0) , EnabledByDefault(EPluginEnabledByDefault::Unspecified) , bCanContainContent(false) , bIsBetaVersion(false) , bInstalled(false) , bRequiresBuildPlatform(false) , bIsHidden(false) { } bool FPluginDescriptor::Load(const FString& FileName, FText& OutFailReason) { // Read the file to a string FString FileContents; if (!FFileHelper::LoadFileToString(FileContents, *FileName)) { OutFailReason = FText::Format(LOCTEXT("FailedToLoadDescriptorFile", "Failed to open descriptor file '{0}'"), FText::FromString(FileName)); return false; } return Read(FileContents, OutFailReason); } bool FPluginDescriptor::Read(const FString& Text, FText& OutFailReason) { // Deserialize a JSON object from the string TSharedPtr< FJsonObject > ObjectPtr; TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(Text); if (!FJsonSerializer::Deserialize(Reader, ObjectPtr) || !ObjectPtr.IsValid() ) { OutFailReason = FText::Format(LOCTEXT("FailedToReadDescriptorFile", "Failed to read file. {0}"), FText::FromString(Reader->GetErrorMessage())); return false; } // Parse it as a plug-in descriptor return Read(*ObjectPtr.Get(), OutFailReason); } bool FPluginDescriptor::Read(const FJsonObject& Object, FText& OutFailReason) { // Read the file version int32 FileVersionInt32; if(!Object.TryGetNumberField(TEXT("FileVersion"), FileVersionInt32)) { if(!Object.TryGetNumberField(TEXT("PluginFileVersion"), FileVersionInt32)) { OutFailReason = LOCTEXT("InvalidProjectFileVersion", "File does not have a valid 'FileVersion' number."); return false; } } // Check that it's within range EPluginDescriptorVersion PluginFileVersion = (EPluginDescriptorVersion)FileVersionInt32; if ((PluginFileVersion <= EPluginDescriptorVersion::Invalid) || (PluginFileVersion > EPluginDescriptorVersion::Latest)) { FText ReadVersionText = FText::FromString(FString::Printf(TEXT("%d"), (int32)PluginFileVersion)); FText LatestVersionText = FText::FromString(FString::Printf(TEXT("%d"), (int32)EPluginDescriptorVersion::Latest)); 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})."), ReadVersionText, LatestVersionText); return false; } // Read the other fields Object.TryGetNumberField(TEXT("Version"), Version); Object.TryGetStringField(TEXT("VersionName"), VersionName); Object.TryGetStringField(TEXT("FriendlyName"), FriendlyName); Object.TryGetStringField(TEXT("Description"), Description); if (!Object.TryGetStringField(TEXT("Category"), Category)) { // Category used to be called CategoryPath in .uplugin files Object.TryGetStringField(TEXT("CategoryPath"), 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 (Category.Len() >= 2 && Category.StartsWith(TEXT("\"")) && Category.EndsWith(TEXT("\""))) { Category = Category.Mid(1, Category.Len() - 2); } Object.TryGetStringField(TEXT("CreatedBy"), CreatedBy); Object.TryGetStringField(TEXT("CreatedByURL"), CreatedByURL); Object.TryGetStringField(TEXT("DocsURL"), DocsURL); Object.TryGetStringField(TEXT("MarketplaceURL"), MarketplaceURL); Object.TryGetStringField(TEXT("SupportURL"), SupportURL); Object.TryGetStringField(TEXT("EngineVersion"), EngineVersion); Object.TryGetStringArrayField(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatforms); Object.TryGetStringArrayField(TEXT("SupportedPrograms"), SupportedPrograms); if (!FModuleDescriptor::ReadArray(Object, TEXT("Modules"), Modules, OutFailReason)) { return false; } if (!FLocalizationTargetDescriptor::ReadArray(Object, TEXT("LocalizationTargets"), LocalizationTargets, OutFailReason)) { return false; } bool bEnabledByDefault; if(Object.TryGetBoolField(TEXT("EnabledByDefault"), bEnabledByDefault)) { EnabledByDefault = bEnabledByDefault? EPluginEnabledByDefault::Enabled : EPluginEnabledByDefault::Disabled; } Object.TryGetBoolField(TEXT("CanContainContent"), bCanContainContent); Object.TryGetBoolField(TEXT("IsBetaVersion"), bIsBetaVersion); Object.TryGetBoolField(TEXT("Installed"), bInstalled); Object.TryGetBoolField(TEXT("RequiresBuildPlatform"), bRequiresBuildPlatform); Object.TryGetBoolField(TEXT("Hidden"), bIsHidden); bool bCanBeUsedWithUnrealHeaderTool; if(Object.TryGetBoolField("CanBeUsedWithUnrealHeaderTool", bCanBeUsedWithUnrealHeaderTool) && bCanBeUsedWithUnrealHeaderTool) { SupportedPrograms.Add(TEXT("UnrealHeaderTool")); } PreBuildSteps.Read(Object, TEXT("PreBuildSteps")); PostBuildSteps.Read(Object, TEXT("PostBuildSteps")); if (!FPluginReferenceDescriptor::ReadArray(Object, TEXT("Plugins"), Plugins, OutFailReason)) { return false; } return true; } bool FPluginDescriptor::Save(const FString& FileName, FText& OutFailReason) const { // Write the descriptor to text FString Text; Write(Text); // Save it to a file if ( !FFileHelper::SaveStringToFile(Text, *FileName) ) { OutFailReason = FText::Format( LOCTEXT("FailedToWriteOutputFile", "Failed to write output file '{0}'. Perhaps the file is Read-Only?"), FText::FromString(FileName) ); return false; } return true; } void FPluginDescriptor::Write(FString& Text) const { // Write the contents of the descriptor to a string. Make sure the writer is destroyed so that the contents are flushed to the string. TSharedRef< TJsonWriter<> > WriterRef = TJsonWriterFactory<>::Create(&Text); TJsonWriter<>& Writer = WriterRef.Get(); Writer.WriteObjectStart(); Write(Writer); Writer.WriteObjectEnd(); Writer.Close(); } void FPluginDescriptor::Write(TJsonWriter<>& Writer) const { Writer.WriteValue(TEXT("FileVersion"), EProjectDescriptorVersion::Latest); Writer.WriteValue(TEXT("Version"), Version); Writer.WriteValue(TEXT("VersionName"), VersionName); Writer.WriteValue(TEXT("FriendlyName"), FriendlyName); Writer.WriteValue(TEXT("Description"), Description); Writer.WriteValue(TEXT("Category"), Category); Writer.WriteValue(TEXT("CreatedBy"), CreatedBy); Writer.WriteValue(TEXT("CreatedByURL"), CreatedByURL); Writer.WriteValue(TEXT("DocsURL"), DocsURL); Writer.WriteValue(TEXT("MarketplaceURL"), MarketplaceURL); Writer.WriteValue(TEXT("SupportURL"), SupportURL); if (EngineVersion.Len() > 0) { Writer.WriteValue(TEXT("EngineVersion"), EngineVersion); } if(EnabledByDefault != EPluginEnabledByDefault::Unspecified) { Writer.WriteValue(TEXT("EnabledByDefault"), (EnabledByDefault == EPluginEnabledByDefault::Enabled)); } Writer.WriteValue(TEXT("CanContainContent"), bCanContainContent); Writer.WriteValue(TEXT("IsBetaVersion"), bIsBetaVersion); Writer.WriteValue(TEXT("Installed"), bInstalled); if(SupportedTargetPlatforms.Num() > 0) { Writer.WriteValue(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatforms); } if (SupportedPrograms.Num() > 0) { Writer.WriteValue(TEXT("SupportedPrograms"), SupportedPrograms); } FModuleDescriptor::WriteArray(Writer, TEXT("Modules"), Modules); FLocalizationTargetDescriptor::WriteArray(Writer, TEXT("LocalizationTargets"), LocalizationTargets); if(bRequiresBuildPlatform) { Writer.WriteValue(TEXT("RequiresBuildPlatform"), bRequiresBuildPlatform); } if (bIsHidden) { Writer.WriteValue(TEXT("Hidden"), bIsHidden); } if(!PreBuildSteps.IsEmpty()) { PreBuildSteps.Write(Writer, TEXT("PreBuildSteps")); } if(!PostBuildSteps.IsEmpty()) { PostBuildSteps.Write(Writer, TEXT("PostBuildSteps")); } FPluginReferenceDescriptor::WriteArray(Writer, TEXT("Plugins"), Plugins); } bool FPluginDescriptor::SupportsTargetPlatform(const FString& Platform) const { return SupportedTargetPlatforms.Num() == 0 || SupportedTargetPlatforms.Contains(Platform); } #undef LOCTEXT_NAMESPACE