// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= ReplaceAssetsCommandlet.cpp: Commandlet for replacing assets with those from another location (intended use is replacing with cooked assets) =============================================================================*/ #include "Commandlets/ReplaceAssetsCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "HAL/FileManager.h" #include "Misc/PackageName.h" #include "Misc/Paths.h" #include "AssetRegistry/ARFilter.h" #include "Engine/World.h" DEFINE_LOG_CATEGORY_STATIC(LogReplaceAssetsCommandlet, Log, All); UReplaceAssetsCommandlet::UReplaceAssetsCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } int32 UReplaceAssetsCommandlet::Main(const FString& InParams) { // Parse command line. TArray Tokens; TArray Switches; UCommandlet::ParseCommandLine(*InParams, Tokens, Switches); // Support standard and BuildGraph style delimeters static const TCHAR* ParamDelims[] = { TEXT(";"), TEXT("+"), }; const FString AssetSourcePathSwitch = TEXT("AssetSourcePath="); const FString ReplacedPathsSwitch = TEXT("ReplacedPaths="); const FString ReplacedClassesSwitch = TEXT("ReplacedClasses="); const FString ExcludedPathsSwitch = TEXT("ExcludedPaths="); const FString ExcludedClassesSwitch = TEXT("ExcludedClasses="); FString AssetSourcePath; TArray ReplacedPaths; TArray ReplacedClasses; TArray ExcludedPaths; TArray ExcludedClasses; // Parse parameters for (int32 SwitchIdx = 0; SwitchIdx < Switches.Num(); ++SwitchIdx) { const FString& Switch = Switches[SwitchIdx]; FString SwitchValue; if (FParse::Value(*Switch, *AssetSourcePathSwitch, SwitchValue)) { AssetSourcePath = SwitchValue; } else if (FParse::Value(*Switch, *ReplacedPathsSwitch, SwitchValue)) { SwitchValue.ParseIntoArray(ReplacedPaths, ParamDelims, 2); } else if (FParse::Value(*Switch, *ReplacedClassesSwitch, SwitchValue)) { SwitchValue.ParseIntoArray(ReplacedClasses, ParamDelims, 2); } else if (FParse::Value(*Switch, *ExcludedPathsSwitch, SwitchValue)) { SwitchValue.ParseIntoArray(ExcludedPaths, ParamDelims, 2); } else if (FParse::Value(*Switch, *ExcludedClassesSwitch, SwitchValue)) { SwitchValue.ParseIntoArray(ExcludedClasses, ParamDelims, 2); } } // Check that replacement asset folder exists bool bHasReplacementAssets = !AssetSourcePath.IsEmpty() && IFileManager::Get().DirectoryExists(*AssetSourcePath); if (!bHasReplacementAssets) { UE_LOG(LogReplaceAssetsCommandlet, Error, TEXT("Source Path for replacement assets does not exist - please specify a valid location for -AssetSourcePath on the commandline")); return 1; } // Load the asset registry module FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Update Registry Module UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Searching Asset Registry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray FinalAssetList; // Get assets from paths and classes that we want to replace if (ReplacedPaths.Num() > 0) { UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Getting Assets from specified paths")); FARFilter Filter; Filter.bIncludeOnlyOnDiskAssets = true; Filter.bRecursivePaths = true; for (const FString& ReplacedPath : ReplacedPaths) { Filter.PackagePaths.AddUnique(*ReplacedPath); } TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); for (FAssetData& Asset : AssetList) { FinalAssetList.AddUnique(Asset); } } if (ReplacedClasses.Num() > 0) { UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Getting Assets of specified classes")); FARFilter Filter; Filter.bIncludeOnlyOnDiskAssets = true; Filter.PackagePaths.Add(TEXT("/Game")); Filter.bRecursivePaths = true; for (const FString& ReplacedClass : ReplacedClasses) { FTopLevelAssetPath ReplacedClassPathName = UClass::TryConvertShortTypeNameToPathName(ReplacedClass, ELogVerbosity::Error, TEXT("UReplaceAssetsCommandlet")); if (ReplacedClassPathName.IsNull()) { UE_LOG(LogReplaceAssetsCommandlet, Error, TEXT("Failed to convert short class name \"%s\" to path name. Please use class path names for ReplacedClasses."), *ReplacedClass); } else { Filter.ClassPaths.AddUnique(ReplacedClassPathName); } } TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); for (FAssetData& Asset : AssetList) { FinalAssetList.AddUnique(Asset); } } // Run through paths and classes that should be excluded if (FinalAssetList.Num() > 0 && ExcludedPaths.Num() > 0) { UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Excluding Assets from specified paths")); FARFilter Filter; Filter.bIncludeOnlyOnDiskAssets = true; Filter.bRecursivePaths = true; for (const FString& ExcludedPath : ExcludedPaths) { Filter.PackagePaths.AddUnique(*ExcludedPath); } TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); FinalAssetList.RemoveAll([&AssetList](const FAssetData& Asset) {return AssetList.Contains(Asset); }); } if (FinalAssetList.Num() > 0 && ExcludedClasses.Num() > 0) { UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Excluding Assets of specified classes")); FARFilter Filter; Filter.bIncludeOnlyOnDiskAssets = true; Filter.PackagePaths.Add(TEXT("/Game")); Filter.bRecursivePaths = true; for (const FString& ExcludedClass : ExcludedClasses) { FTopLevelAssetPath ExcludedClassPathName = UClass::TryConvertShortTypeNameToPathName(ExcludedClass, ELogVerbosity::Error, TEXT("UReplaceAssetsCommandlet")); if (ExcludedClassPathName.IsNull()) { UE_LOG(LogReplaceAssetsCommandlet, Error, TEXT("Failed to convert short class name \"%s\" to path name. Please use class path names for ExcludedClasses."), *ExcludedClass); } else { Filter.ClassPaths.AddUnique(ExcludedClassPathName); } } TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); FinalAssetList.RemoveAll([&AssetList](const FAssetData& Asset) {return AssetList.Contains(Asset); }); } TArray FinalFileList; if (FinalAssetList.Num() > 0) { UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Converting Package Names to File Paths")); for (FAssetData& RemovedAsset : FinalAssetList) { bool bIsMap = RemovedAsset.AssetClassPath == UWorld::StaticClass()->GetClassPathName(); FinalFileList.AddUnique(FPaths::ConvertRelativePathToFull(FPackageName::LongPackageNameToFilename(RemovedAsset.PackageName.ToString(), bIsMap ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension()))); } UE_LOG(LogReplaceAssetsCommandlet, Display, TEXT("Replacing files...")); for (const FString& ReplacedPath : FinalFileList) { UE_LOG(LogReplaceAssetsCommandlet, Log, TEXT("Replacing asset: %s"), *ReplacedPath); if (!IFileManager::Get().Delete(*ReplacedPath, false, true)) { UE_LOG(LogReplaceAssetsCommandlet, Error, TEXT("Failed to delete asset: %s"), *ReplacedPath); } FString ReplacementPath = ReplacedPath; FPaths::MakePathRelativeTo(ReplacementPath, *FPaths::RootDir()); ReplacementPath = FPaths::Combine(AssetSourcePath, ReplacementPath); if (IFileManager::Get().FileExists(*ReplacementPath)) { if (IFileManager::Get().Copy(*ReplacedPath, *ReplacementPath) != COPY_OK) { UE_LOG(LogReplaceAssetsCommandlet, Error, TEXT("Failed to copy asset: %s"), *ReplacementPath); } } } } return 0; }