// Copyright Epic Games, Inc. All Rights Reserved. #include "FeaturePackContentSource.h" #include "AssetCompilingManager.h" #include "AssetToolsModule.h" #include "Containers/Map.h" #include "Containers/StringFwd.h" #include "Containers/StringView.h" #include "ContentBrowserModule.h" #include "ContentSourceProviderManager.h" #include "CoreGlobals.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" #include "FileHelpers.h" #include "GenericPlatform/GenericPlatformFile.h" #include "HAL/FileManager.h" #include "HAL/PlatformFileManager.h" #include "IAddContentDialogModule.h" #include "IAssetTools.h" #include "IContentBrowserSingleton.h" #include "IContentSource.h" #include "IContentSourceProvider.h" #include "IPlatformFilePak.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonTypes.h" #include "Templates/Tuple.h" #include "Templates/UniquePtr.h" #include "Trace/Detail/Channel.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/UObjectGlobals.h" class UPackage; #define LOCTEXT_NAMESPACE "ContentFeaturePacks" DEFINE_LOG_CATEGORY_STATIC(LogFeaturePack, Log, All); bool TryValidateTranslatedValue(TSharedPtr TranslatedValue, TSharedPtr& ErrorMessage) { if (TranslatedValue.IsValid() == false) { ErrorMessage = MakeShareable(new FString("Invalid translated value")); return false; } const TSharedPtr* TranslatedObject; if (TranslatedValue->TryGetObject(TranslatedObject) == false) { ErrorMessage = MakeShareable(new FString("Invalid translated value")); return false; } if ((*TranslatedObject)->HasTypedField(TEXT("Language")) == false) { ErrorMessage = MakeShareable(new FString("Translated value missing 'Language' field")); return false; } if ((*TranslatedObject)->HasTypedField(TEXT("Text")) == false) { ErrorMessage = MakeShareable(new FString("Translated value missing 'Text' field")); return false; } return true; } bool TryValidateManifestObject(TSharedPtr ManifestObject, TSharedPtr& ErrorMessage) { if (ManifestObject.IsValid() == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing")); return false; } if (ManifestObject->HasTypedField(TEXT("Name")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'Names' field")); return false; } for (TSharedPtr NameValue : ManifestObject->GetArrayField(TEXT("Name"))) { if (TryValidateTranslatedValue(NameValue, ErrorMessage) == false) { ErrorMessage = MakeShareable(new FString(FString::Printf(TEXT("Manifest object 'Names' field error: %s"), **ErrorMessage))); } } if (ManifestObject->HasTypedField(TEXT("Description")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'Description' field")); return false; } for (TSharedPtr DescriptionValue : ManifestObject->GetArrayField(TEXT("Description"))) { if (TryValidateTranslatedValue(DescriptionValue, ErrorMessage) == false) { ErrorMessage = MakeShareable(new FString(FString::Printf(TEXT("Manifest object 'Description' field error: %s"), **ErrorMessage))); } } if (ManifestObject->HasTypedField(TEXT("AssetTypes")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'AssetTypes' field")); return false; } for (TSharedPtr AssetTypesValue : ManifestObject->GetArrayField(TEXT("AssetTypes"))) { if (TryValidateTranslatedValue(AssetTypesValue, ErrorMessage) == false) { ErrorMessage = MakeShareable(new FString(FString::Printf(TEXT("Manifest object 'AssetTypes' field error: %s"), **ErrorMessage))); } } if (ManifestObject->HasTypedField(TEXT("ClassTypes")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'ClassTypes' field")); return false; } if (ManifestObject->HasTypedField(TEXT("Category")) == false && ManifestObject->HasTypedField(TEXT("Category")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'Category' field")); return false; } if (ManifestObject->HasTypedField(TEXT("Thumbnail")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'Thumbnail' field")); return false; } if (ManifestObject->HasTypedField(TEXT("Screenshots")) == false) { ErrorMessage = MakeShareable(new FString("Manifest object missing 'Screenshots' field")); return false; } // If we have an additional files entry check its valid if (ManifestObject->HasTypedField(TEXT("AdditionalFiles")) == true) { TSharedPtr AdditionalFileObject = ManifestObject->GetObjectField(TEXT("AdditionalFiles")); if (AdditionalFileObject->HasTypedField(TEXT("DestinationFilesFolder")) == false ) { ErrorMessage = MakeShareable(new FString("Manifest has an AdditionalFiles object but no DestinationFilesFolder")); return false; } if (AdditionalFileObject->HasTypedField(TEXT("AdditionalFilesList")) == false) { ErrorMessage = MakeShareable(new FString("Manifest has an AdditionalFiles object but no AdditionalFilesList")); return false; } } return true; } FFeaturePackContentSource::FFeaturePackContentSource(FString InFeaturePackPath) { FeaturePackPath = InFeaturePackPath; bPackValid = false; FString ManifestString; Categories = { EContentSourceCategory::Unknown }; if( InFeaturePackPath.EndsWith(TEXT(".upack") ) == true ) { bContentsInPakFile = true; MountPoint = FPaths::GameFeatureRootPrefix(); const FString PakFileName = TEXT("PakFile"); FPakPlatformFile* PakPlatformFile = static_cast(FPlatformFileManager::Get().FindPlatformFile(*PakFileName)); if (!PakPlatformFile) { // Create a pak platform file and mount the feature pack file. PakPlatformFile = static_cast(FPlatformFileManager::Get().GetPlatformFile(*PakFileName)); PakPlatformFile->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT("")); } PakPlatformFile->Mount(*InFeaturePackPath, 0, *MountPoint); // Gets the manifest file as a JSon string TArray ManifestBuffer; if (LoadPakFileToBuffer(*PakPlatformFile, FPaths::Combine(*MountPoint, TEXT("manifest.json")), ManifestBuffer) == false) { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find manifest."), *FeaturePackPath)); return; } FFileHelper::BufferToString(ManifestString, ManifestBuffer.GetData(), ManifestBuffer.Num()); if (ParseManifestString(ManifestString) == true) { LoadFeaturePackImageDataFromPackFile(*PakPlatformFile); } PakPlatformFile->Unmount(*InFeaturePackPath); } else { bContentsInPakFile = false; FString TemplatesFolder = TEXT("FeaturePack"); FString ThisTemplateRoot = FPaths::GetPath(FeaturePackPath); if( ThisTemplateRoot.EndsWith(TemplatesFolder) == true ) { int32 Index = ThisTemplateRoot.Find(TemplatesFolder); ThisTemplateRoot.LeftInline(Index); } MountPoint = ThisTemplateRoot; FFileHelper::LoadFileToString( ManifestString, *FeaturePackPath); if (ParseManifestString(ManifestString) == true) { LoadFeaturePackImageData(); } } } FFeaturePackContentSource::FFeaturePackContentSource() { bPackValid = false; } bool FFeaturePackContentSource::LoadPakFileToBuffer(FPakPlatformFile& PakPlatformFile, FString Path, TArray& Buffer) { bool bResult = false; TSharedPtr FileHandle(PakPlatformFile.OpenRead(*Path)); if( FileHandle.IsValid()) { Buffer.AddUninitialized(FileHandle->Size()); bResult = FileHandle->Read(Buffer.GetData(), FileHandle->Size()); } return bResult; } const TArray& FFeaturePackContentSource::GetLocalizedNames() const { return LocalizedNames; } const TArray& FFeaturePackContentSource::GetLocalizedDescriptions() const { return LocalizedDescriptions; } const TArray& FFeaturePackContentSource::GetLocalizedAssetTypes() const { return LocalizedAssetTypesList; } const FString& FFeaturePackContentSource::GetClassTypesUsed() const { return ClassTypes; } const TArray& FFeaturePackContentSource::GetCategories() const { return Categories; } TSharedPtr FFeaturePackContentSource::GetIconData() const { return IconData; } const TArray>& FFeaturePackContentSource::GetScreenshotData() const { return ScreenshotData; } bool FFeaturePackContentSource::InstallToProject(FString InstallPath) { bool bResult = false; if( IsDataValid() == false ) { UE_LOG(LogFeaturePack, Warning, TEXT("Trying to install invalid pack %s"), *InstallPath); } else { // We need to insert additional packs before we import the main assets since the code in the main pack may reference them // TODO - handle errors from this TArray FilesCopied; InsertAdditionalResources(AdditionalFeaturePacks,EFeaturePackDetailLevel::High, FPaths::ProjectDir(),FilesCopied); if (AdditionalFilesForPack.AdditionalFilesList.Num() != 0) { bool bHasSourceFiles = false; CopyAdditionalFilesToFolder( FPaths::ProjectDir(), FilesCopied, bHasSourceFiles/*,AdditionalFilesFolder*/); } if( bContentsInPakFile == true) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); TArray AssetPaths; AssetPaths.Add(FeaturePackPath); TArray ImportedObjects = AssetToolsModule.Get().ImportAssets(AssetPaths, InstallPath); if (ImportedObjects.Num() == 0) { UE_LOG(LogFeaturePack, Warning, TEXT("No objects imported installing pack %s"), *InstallPath); } else { // Save any imported assets. TArray ToSave; for (UObject* ImportedObject : ImportedObjects) { ToSave.AddUnique(ImportedObject->GetOutermost()); } FEditorFileUtils::PromptForCheckoutAndSave(ToSave, /*bCheckDirty=*/ false, /*bPromptToSave=*/ false); bResult = true; } } // Focus on a specific asset if we want to. if (GetFocusAssetName().IsEmpty() == false) { UObject* FocusAsset = LoadObject(nullptr, *GetFocusAssetName()); if (FocusAsset) { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked("ContentBrowser"); TArray SyncObjects; SyncObjects.Add(FocusAsset); ContentBrowserModule.Get().SyncBrowserToAssets(SyncObjects); } } } return bResult; } FFeaturePackContentSource::~FFeaturePackContentSource() { } bool FFeaturePackContentSource::IsDataValid() const { if( bPackValid == false ) { return false; } // To Do maybe validate other data here return true; } const FString& FFeaturePackContentSource::GetFocusAssetName() const { return FocusAssetIdent; } const FString& FFeaturePackContentSource::GetSortKey() const { return SortKey; } const FString& FFeaturePackContentSource::GetIdent() const { return Identity; } FLocalizedText FFeaturePackContentSource::ChooseLocalizedText(TArray Choices, FString LanguageCode) { FLocalizedText Default; for (const FLocalizedText& Choice : Choices) { if (Choice.GetTwoLetterLanguage() == LanguageCode) { return Choice; } else if (Choice.GetTwoLetterLanguage() == TEXT("en")) { Default = Choice; } } return Default; } FLocalizedTextArray FFeaturePackContentSource::ChooseLocalizedTextArray(TArray Choices, FString LanguageCode) { FLocalizedTextArray Default; for (const FLocalizedTextArray& Choice : Choices) { if (Choice.GetTwoLetterLanguage() == LanguageCode) { return Choice; } else if (Choice.GetTwoLetterLanguage() == TEXT("en")) { Default = Choice; } } return Default; } bool FFeaturePackContentSource::GetAdditionalFilesForPack(TArray& FileList, bool& bContainsSource) { bool bParsedFiles = false; if (bPackValid == false) { RecordAndLogError(FString::Printf(TEXT("Cannot extract files from invalid Pack %s"), *FeaturePackPath)); } else { // This doesnt support additional files in the manifest and in the config file. Should only do one or the other. if( AdditionalFilesForPack.AdditionalFilesList.Num() != 0 ) { BuildListOfAdditionalFiles(AdditionalFilesForPack.AdditionalFilesList,FileList,bContainsSource); } else if( bContentsInPakFile == true) { // Create a pak platform file and mount the feature pack file. FPakPlatformFile PakPlatformFile; PakPlatformFile.Initialize(&FPlatformFileManager::Get().GetPlatformFile(), TEXT("")); PakPlatformFile.Mount(*FeaturePackPath, 0, *MountPoint); // Gets the manifest file as a JSon string TArray ManifestBuffer; if (LoadPakFileToBuffer(PakPlatformFile, FPaths::Combine(*MountPoint, TEXT("Config/Config.ini")), ManifestBuffer) == false) { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find Config.ini"), *FeaturePackPath)); } else { FString ConfigFileString; FFileHelper::BufferToString(ConfigFileString, ManifestBuffer.GetData(), ManifestBuffer.Num()); bParsedFiles = ExtractListOfAdditionalFiles(ConfigFileString, FileList, bContainsSource); } } } return bParsedFiles; } bool FFeaturePackContentSource::ExtractListOfAdditionalFiles(const FString& InConfigFileAsString,TArray& InFileList, bool& bContainsSource) { FConfigFile PackConfig; PackConfig.ProcessInputFileContents(InConfigFileAsString, TEXT("Uknown, see FFeaturePackContentSource::ExtractListOfAdditionalFiles")); const FConfigSection* AdditionalFilesSection = PackConfig.FindSection("AdditionalFilesToAdd"); bContainsSource = false; bool bParsedAdditionFiles = false; if (AdditionalFilesSection) { TArray AdditionalFilesMap; bParsedAdditionFiles = true; for (auto& FilePair : *AdditionalFilesSection) { if (FilePair.Key.ToString().Contains("Files")) { AdditionalFilesMap.Add(FilePair.Value.GetValue()); } } BuildListOfAdditionalFiles(AdditionalFilesMap,InFileList,bContainsSource); } return bParsedAdditionFiles; } void FFeaturePackContentSource::BuildListOfAdditionalFiles(TArray& AdditionalFileSourceList, TArray& FileList, bool& bContainsSourceFiles) { for (const FString& FileSource : AdditionalFileSourceList) { FString Filename = FPaths::GetCleanFilename(FileSource); FString Directory = FPaths::RootDir() / FPaths::GetPath(FileSource); FPaths::MakeStandardFilename(Directory); FPakFile::MakeDirectoryFromPath(Directory); if (Filename.Contains(TEXT("*"))) { TArray FoundFiles; IFileManager::Get().FindFilesRecursive(FoundFiles, *Directory, *Filename, true, false); FileList.Append(FoundFiles); if (!bContainsSourceFiles) { for (const FString& FoundFile : FoundFiles) { if (FoundFile.StartsWith(TEXT("Source/")) || FoundFile.Contains(TEXT("/Source/"))) { bContainsSourceFiles = true; break; } } } } else { FileList.Add(Directory / Filename); if (!bContainsSourceFiles && (FileList.Last().StartsWith(TEXT("Source/")) || FileList.Last().Contains(TEXT("/Source/")))) { bContainsSourceFiles = true; } } } } void FFeaturePackContentSource::ImportPendingPacks() { bool bAddPacks; if (GConfig->GetBool(TEXT("StartupActions"), TEXT("bAddPacks"), bAddPacks, GGameIni) == true) { if (bAddPacks == true) { ParseAndImportPacks(); GConfig->SetBool(TEXT("StartupActions"), TEXT("bAddPacks"), false, GGameIni); GConfig->Flush(true, GGameIni); } } } void FFeaturePackContentSource::ParseAndImportPacks() { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); // Look for pack insertions in the startup actions section TArray PacksToAdd; GConfig->GetArray(TEXT("StartupActions"), TEXT("InsertPack"), PacksToAdd, GGameIni); int32 PacksInserted = 0; for (int32 iPackEntry = 0; iPackEntry < PacksToAdd.Num(); iPackEntry++) { FPackData EachPackData; TArray PackEntries; PacksToAdd[iPackEntry].ParseIntoArray(PackEntries, TEXT(","), true); FString PackSource; FString PackName; // Parse the pack name and source for (int32 iEntry = 0; iEntry < PackEntries.Num(); iEntry++) { FString EachString = PackEntries[iEntry]; // remove the parenthesis EachString = EachString.Replace(TEXT("("), TEXT("")); EachString = EachString.Replace(TEXT(")"), TEXT("")); if (EachString.StartsWith(TEXT("PackSource=")) == true) { EachString = EachString.Replace(TEXT("PackSource="), TEXT("")); EachString = EachString.TrimQuotes(); EachPackData.PackSource = EachString; } if (EachString.StartsWith(TEXT("PackName=")) == true) { EachString = EachString.Replace(TEXT("PackName="), TEXT("")); EachString = EachString.TrimQuotes(); EachPackData.PackName = EachString; } } // If we found anything to insert, insert it ! if ((EachPackData.PackSource.IsEmpty() == false) && (EachPackData.PackName.IsEmpty() == false)) { TArray EachImport; FString FullPath = FPaths::FeaturePackDir() + EachPackData.PackSource; EachImport.Add(FullPath); EachPackData.ImportedObjects = AssetToolsModule.Get().ImportAssets(EachImport, TEXT("/Game"), nullptr, false); if (EachPackData.ImportedObjects.Num() == 0) { UE_LOG(LogFeaturePack, Warning, TEXT("No objects imported installing pack %s"), *EachPackData.PackSource); } else { // Save any imported assets. TArray ToSave; for (UObject* ImportedObject : EachPackData.ImportedObjects) { ToSave.AddUnique(ImportedObject->GetOutermost()); } // Make sure any async compilation kicked off during ImportAssets is completed before we save. FAssetCompilingManager::Get().FinishAllCompilation(); FEditorFileUtils::PromptForCheckoutAndSave(ToSave, /*bCheckDirty=*/ false, /*bPromptToSave=*/ false); PacksInserted++; } } } UE_LOG(LogFeaturePack, Log, TEXT("Inserted %d feature packs"), PacksInserted); } void FFeaturePackContentSource::RecordAndLogError(const FString& ErrorString) { UE_LOG(LogFeaturePack, Error, TEXT("%s"), *ErrorString); ParseErrors.Add(ErrorString); } void FFeaturePackContentSource::CopyAdditionalFilesToFolder( const FString& DestinationFolder, TArray& FilesCopied, bool &bHasSourceFiles, FString InGameFolder ) { FString ContentIdent = TEXT("Content/"); TArray FilesToAdd; GetAdditionalFilesForPack(FilesToAdd, bHasSourceFiles); for (int32 iFile = 0; iFile < FilesToAdd.Num(); ++iFile) { FString EachFile = FilesToAdd[iFile]; int32 ContentIndex; ContentIndex = EachFile.Find(*ContentIdent); if (ContentIndex != INDEX_NONE) { FString ContentFile = EachFile.RightChop(ContentIndex); FString GameFolder = InGameFolder; if(( GameFolder.IsEmpty() == false) && ( GameFolder.StartsWith(TEXT("/")) == false )) { GameFolder.InsertAt(0,TEXT("/")); } FPaths::NormalizeFilename(ContentFile); if( GameFolder.IsEmpty() == false) { ContentFile.InsertAt(ContentIdent.Len() - 1, GameFolder); } FString FinalDestination = DestinationFolder / ContentFile; if (IFileManager::Get().Copy(*FinalDestination, *EachFile) == COPY_OK) { FilesCopied.Add(FinalDestination); } else { UE_LOG(LogFeaturePack, Warning, TEXT("Failed to copy %s to %s"), *EachFile, *FinalDestination); } } } } void FFeaturePackContentSource::InsertAdditionalFeaturePacks() { // copy files in any additional packs (These are for the shared assets) EFeaturePackDetailLevel RequiredLevel = EFeaturePackDetailLevel::High; for (int32 iExtraPack = 0; iExtraPack < AdditionalFeaturePacks.Num(); iExtraPack++) { FString DestinationFolder = FPaths::ProjectDir(); FString FullPath = FPaths::FeaturePackDir() + AdditionalFeaturePacks[iExtraPack].GetFeaturePackNameForLevel(RequiredLevel); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*FullPath)) { FString InsertPath = DestinationFolder + *AdditionalFeaturePacks[iExtraPack].MountName; TUniquePtr NewContentSource = MakeUnique(FullPath); if (NewContentSource->IsDataValid() == true) { bool bHasSourceFiles = false; TArray FilesCopied; FString GameFolder = AdditionalFeaturePacks[iExtraPack].MountName; NewContentSource->CopyAdditionalFilesToFolder(DestinationFolder, FilesCopied, bHasSourceFiles, GameFolder); } } } } bool FFeaturePackContentSource::InsertAdditionalResources(TArray InAdditionalFeaturePacks,EFeaturePackDetailLevel RequiredLevel, const FString& InDestinationFolder,TArray& InFilesCopied ) { // Build a map of feature packs we have (This would probably be better elsewhere and stored in 2 arrays - one listing .upack packs and one non-upack packs) IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked("AddContentDialog"); TMap PackMap; for (const TSharedRef& ContentSourceProvider : *AddContentDialogModule.GetContentSourceProviderManager()->GetContentSourceProviders()) { const TArray> ProviderSources = ContentSourceProvider->GetContentSources(); for (const TSharedRef& EachSourceProvider : ProviderSources) { FFeaturePackContentSource* Source = (FFeaturePackContentSource*) &EachSourceProvider.Get(); FStringView ID = EachSourceProvider->GetIdent(); if (!ID.IsEmpty()) { PackMap.Add(FString(ID), Source); } } } int32 PacksInserted = 0; for (int32 iExtraPack = 0; iExtraPack < InAdditionalFeaturePacks.Num(); iExtraPack++) { // TODO - improve error handling here FString PackName = InAdditionalFeaturePacks[iExtraPack].GetFeaturePackNameForLevel(RequiredLevel); // If we have a 'non upack' pack, insert it else try a upack version FFeaturePackContentSource* Insertable = PackMap.FindRef(PackName.Replace(TEXT(".upack"),TEXT(""))); TArray FilesCopied; if( Insertable != nullptr ) { bool bHasSourceFiles = false; Insertable->CopyAdditionalFilesToFolder(InDestinationFolder, FilesCopied, bHasSourceFiles, InAdditionalFeaturePacks[iExtraPack].MountName); PacksInserted++; } else { FString FullPath = FPaths::FeaturePackDir() + InAdditionalFeaturePacks[iExtraPack].GetFeaturePackNameForLevel(RequiredLevel); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*FullPath)) { FString InsertPath = InDestinationFolder + *InAdditionalFeaturePacks[iExtraPack].MountName; TUniquePtr NewContentSource = MakeUnique(FullPath); if (NewContentSource->IsDataValid() == true) { bool bHasSourceFiles = false; NewContentSource->CopyAdditionalFilesToFolder(InDestinationFolder, FilesCopied, bHasSourceFiles, InAdditionalFeaturePacks[iExtraPack].MountName); PacksInserted++; } } } // Add to the list of files copied InFilesCopied.Append(FilesCopied); } return PacksInserted == InAdditionalFeaturePacks.Num(); } bool FFeaturePackContentSource::ParseManifestString(const FString& ManifestString) { // Populate text fields from the manifest. TSharedPtr ManifestObject; TSharedRef> ManifestReader = TJsonReaderFactory<>::Create(ManifestString); FJsonSerializer::Deserialize(ManifestReader, ManifestObject); if (ManifestReader->GetErrorMessage().IsEmpty() == false) { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Failed to parse manifest: %s"), *FeaturePackPath, *ManifestReader->GetErrorMessage())); Categories = { EContentSourceCategory::Unknown }; return false; } if (ManifestObject->HasTypedField(TEXT("Version")) == true) { VersionNumber = ManifestObject->GetStringField(TEXT("Version")); } if (ManifestObject->HasTypedField(TEXT("Ident")) == true) { Identity = ManifestObject->GetStringField(TEXT("Ident")); } TSharedPtr ManifestObjectErrorMessage; if (TryValidateManifestObject(ManifestObject, ManifestObjectErrorMessage) == false) { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Manifest object error: %s"), *FeaturePackPath, **ManifestObjectErrorMessage)); Categories = { EContentSourceCategory::Unknown }; return false; } for (TSharedPtr NameValue : ManifestObject->GetArrayField(TEXT("Name"))) { TSharedPtr LocalizedNameObject = NameValue->AsObject(); LocalizedNames.Add(FLocalizedText( LocalizedNameObject->GetStringField(TEXT("Language")), FText::FromString(LocalizedNameObject->GetStringField(TEXT("Text"))))); } for (TSharedPtr DescriptionValue : ManifestObject->GetArrayField(TEXT("Description"))) { TSharedPtr LocalizedDescriptionObject = DescriptionValue->AsObject(); LocalizedDescriptions.Add(FLocalizedText( LocalizedDescriptionObject->GetStringField(TEXT("Language")), FText::FromString(LocalizedDescriptionObject->GetStringField(TEXT("Text"))))); } // Parse asset types field for (TSharedPtr AssetTypesValue : ManifestObject->GetArrayField(TEXT("AssetTypes"))) { TSharedPtr LocalizedAssetTypesObject = AssetTypesValue->AsObject(); LocalizedAssetTypesList.Add(FLocalizedText( LocalizedAssetTypesObject->GetStringField(TEXT("Language")), FText::FromString(LocalizedAssetTypesObject->GetStringField(TEXT("Text"))))); } // Parse search tags field if (ManifestObject->HasField(TEXT("SearchTags")) == true) { for (TSharedPtr AssetTypesValue : ManifestObject->GetArrayField(TEXT("SearchTags"))) { TSharedPtr LocalizedAssetTypesObject = AssetTypesValue->AsObject(); LocalizedSearchTags.Add(FLocalizedTextArray( LocalizedAssetTypesObject->GetStringField(TEXT("Language")), LocalizedAssetTypesObject->GetStringField(TEXT("Text")))); } } // Parse class types field ClassTypes = ManifestObject->GetStringField(TEXT("ClassTypes")); // Parse initial focus asset if we have one - this is not required if (ManifestObject->HasTypedField(TEXT("FocusAsset")) == true) { FocusAssetIdent = ManifestObject->GetStringField(TEXT("FocusAsset")); } // Use the path as the sort key - it will be alphabetical that way SortKey = FeaturePackPath; ManifestObject->TryGetStringField(TEXT("SortKey"), SortKey); TArray CategoryStrings; if (!ManifestObject->TryGetStringArrayField(TEXT("Category"), CategoryStrings)) { FString CategoryString = ManifestObject->GetStringField(TEXT("Category")); if (!CategoryString.IsEmpty()) { CategoryStrings.Add(CategoryString); } } UEnum* Enum = FindObjectChecked(nullptr, TEXT("/Script/AddContentDialog.EContentSourceCategory")); for (const FString& CategoryString : CategoryStrings) { int32 EnumValue = Enum->GetValueByName(FName(*CategoryString)); if (EnumValue != INDEX_NONE) { Categories.Add((EContentSourceCategory) EnumValue); } } // Thumbnail filename IconFilename = ManifestObject->GetStringField(TEXT("Thumbnail")); // Screenshots filenames ScreenshotFilenameArray = ManifestObject->GetArrayField(TEXT("Screenshots")); // Parse additional files data if (ManifestObject->HasTypedField(TEXT("AdditionalFiles")) == true) { TSharedPtr AdditionalFileObject = ManifestObject->GetObjectField(TEXT("AdditionalFiles")); if( AdditionalFileObject->HasTypedField(TEXT("DestinationFilesFolder")) == true ) { AdditionalFilesForPack.DestinationFilesFolder = AdditionalFileObject->GetStringField(TEXT("DestinationFilesFolder")); if (AdditionalFileObject->HasTypedField(TEXT("AdditionalFilesList")) == true) { for (TSharedPtr FileEntryValue : AdditionalFileObject->GetArrayField(TEXT("AdditionalFilesList"))) { const FString FileSpecString = FileEntryValue->AsString(); AdditionalFilesForPack.AdditionalFilesList.AddUnique(FileSpecString); } } } } // Parse additional packs data if (ManifestObject->HasTypedField(TEXT("AdditionalFeaturePacks")) == true) { UEnum* DetailEnum = FindObjectChecked(nullptr, TEXT("/Script/AddContentDialog.EFeaturePackDetailLevel")); for (TSharedPtr AdditionalFeaturePackValue : ManifestObject->GetArrayField(TEXT("AdditionalFeaturePacks"))) { TSharedPtr EachAdditionalPack = AdditionalFeaturePackValue->AsObject(); FString MountName = EachAdditionalPack->GetStringField(TEXT("MountName")); TArray Levels; for (TSharedPtr DetailValue : EachAdditionalPack->GetArrayField(TEXT("DetailLevels"))) { const FString DetailString = DetailValue->AsString(); int32 eValue = DetailEnum->GetValueByName(FName(*DetailString)); EFeaturePackDetailLevel EachLevel = eValue != INDEX_NONE ? (EFeaturePackDetailLevel)eValue : EFeaturePackDetailLevel::Standard; Levels.AddUnique(EachLevel); } AdditionalFeaturePacks.Add(FFeaturePackLevelSet(MountName,Levels)); } } bPackValid = true; return true; } bool FFeaturePackContentSource::LoadFeaturePackImageData() { TSharedPtr> IconImageData = MakeShareable(new TArray()); FString ThumbnailFile = FPaths::Combine(*MountPoint, TEXT("Media"), *IconFilename); if (FFileHelper::LoadFileToArray( *IconImageData.Get(),*ThumbnailFile ) == true) { IconData = MakeShareable(new FImageData(IconFilename, IconImageData)); } else { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find thumbnail %s."), *FeaturePackPath, *ThumbnailFile)); } // parse the screenshots field for (const TSharedPtr& ScreenshotFilename : ScreenshotFilenameArray) { TSharedPtr> SingleScreenshotData = MakeShareable(new TArray); if (FFileHelper::LoadFileToArray(*SingleScreenshotData.Get(), *FPaths::Combine(*MountPoint, TEXT("Media"), *ScreenshotFilename->AsString()) )) { ScreenshotData.Add(MakeShareable(new FImageData(ScreenshotFilename->AsString(), SingleScreenshotData))); } else { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find screenshot %s."), *FeaturePackPath, *ScreenshotFilename->AsString())); } } return true; } bool FFeaturePackContentSource::LoadFeaturePackImageDataFromPackFile(FPakPlatformFile& PakPlatformFile) { bool bAllImagesValid = true; TSharedPtr> IconImageData = MakeShareable(new TArray()); FString ThumbnailFile = FPaths::Combine(*MountPoint, TEXT("Media"), *IconFilename); if (LoadPakFileToBuffer(PakPlatformFile, ThumbnailFile, *IconImageData) == true) { IconData = MakeShareable(new FImageData(IconFilename, IconImageData)); } else { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find thumbnail %s."), *FeaturePackPath, *ThumbnailFile)); bAllImagesValid = false; } // parse the screenshots field for (const TSharedPtr& ScreenshotFilename : ScreenshotFilenameArray) { TSharedPtr> SingleScreenshotData = MakeShareable(new TArray); if (LoadPakFileToBuffer(PakPlatformFile, FPaths::Combine(*MountPoint, TEXT("Media"), *ScreenshotFilename->AsString()), *SingleScreenshotData)) { ScreenshotData.Add(MakeShareable(new FImageData(ScreenshotFilename->AsString(), SingleScreenshotData))); } else { RecordAndLogError(FString::Printf(TEXT("Error in Feature pack %s. Cannot find screenshot %s."), *FeaturePackPath, *ScreenshotFilename->AsString())); bAllImagesValid = false; } } return bAllImagesValid; } #undef LOCTEXT_NAMESPACE