// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/ExportPakDependencies.h" #include "AssetRegistryModule.h" #include "Containers/Set.h" #include "Containers/Array.h" #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" #include "Serialization/JsonWriter.h" #include "Serialization/JsonSerializer.h" #include "HAL/FileManager.h" #include "IPlatformFilePak.h" #include "HAL/PlatformFilemanager.h" #include "Misc/CommandLine.h" #include "Misc/Paths.h" #include "Misc/App.h" struct FPackage { public: TSet DirectlyReferencing; TSet DirectlyReferencedBy; TSet AllReferences; FName Name; int64 InclusiveSize; int64 ExclusiveSize; int Id; private: bool bUpdateHelper; static TMap NameToPackageMap; public: FPackage(const FName& InName, int InId) : Name(InName), InclusiveSize(0), ExclusiveSize(0), Id(InId), bUpdateHelper(false) {} static FPackage* FindOrCreatePackage(FName PackageName) { static int Id = 1; FPackage* Package = NameToPackageMap.FindRef(PackageName); if (!Package) { Package = new FPackage(PackageName, Id++); NameToPackageMap.Add(PackageName, Package); } return Package; } void ResetUpdateHelper() { bUpdateHelper = false; } void RecurseUpdateReferences() { if (!bUpdateHelper) { bUpdateHelper = true; for (auto& DirectReference: DirectlyReferencing) { AllReferences.Add(DirectReference); DirectReference->RecurseUpdateReferences(); AllReferences.Append(DirectReference->AllReferences); } } } void UpdateInclusiveSize() { InclusiveSize = ExclusiveSize; for (auto& Reference: AllReferences) { InclusiveSize += Reference->ExclusiveSize; } } static void GetAllPackages(TArray& OutPackages) { OutPackages.Reset(NameToPackageMap.Num()); for (const auto& Entry: NameToPackageMap) { OutPackages.Add(Entry.Value); } } TArray> ToJsonHelper(const TSet& Packages) { TArray> JsonPackageNames; for (const auto Package: Packages) { JsonPackageNames.Add(MakeShareable(new FJsonValueString(Package->Name.ToString()))); } return JsonPackageNames; } TSharedPtr ToJsonObject() { TSharedPtr JsonPackageObject = MakeShareable(new FJsonObject); JsonPackageObject->SetStringField(TEXT("Name"), *Name.ToString()); JsonPackageObject->SetNumberField(TEXT("InclusiveSize"), InclusiveSize); JsonPackageObject->SetNumberField(TEXT("ExclusiveSize"), ExclusiveSize); JsonPackageObject->SetArrayField(TEXT("DirectlyReferencing"), ToJsonHelper(DirectlyReferencing)); JsonPackageObject->SetArrayField(TEXT("DirectlyReferencedBy"), ToJsonHelper(DirectlyReferencedBy)); JsonPackageObject->SetArrayField(TEXT("AllReferences"), ToJsonHelper(AllReferences)); return JsonPackageObject; } }; TMap FPackage::NameToPackageMap; bool ExportDependencies(const TCHAR* PakFilename, const TCHAR* GameName, const TCHAR* OutputFilenameBase, bool bSigned) { // Example command line used for this tool // C:\Development\BB\WEX\Saved\StagedBuilds\WindowsNoEditor\WorldExplorers\Content\Paks\WorldExplorers-WindowsNoEditor.pak WorldExplorers WEX -exportdependencies=c:\dvtemp\output -debug -NoAssetRegistryCache -ForceDependsGathering TRefCountPtr PakFilePtr = new FPakFile(&FPlatformFileManager::Get().GetPlatformFile(), PakFilename, bSigned); FPakFile& PakFile = *PakFilePtr; if (PakFile.IsValid()) { // Get size information from PAK file. { TArray Records; FString PakGameContentFolder = FString(GameName) + TEXT("/Content"); if (!PakFile.HasFilenames()) { UE_LOG(LogPakFile, Error, TEXT("Pakfiles were loaded without Filenames, cannot export.")); return false; } for (FPakFile::FPakEntryIterator It(PakFile); It; ++It) { FString PackageName; It.TryGetFilename()->Split(TEXT("."), &PackageName, NULL); int64 Size = It.Info().Size; if (PackageName.StartsWith(TEXT("Engine/Content"))) { PackageName = PackageName.Replace(TEXT("Engine/Content"), TEXT("/Engine")); } else if (PackageName.StartsWith(*PakGameContentFolder)) { PackageName = PackageName.Replace(*PakGameContentFolder, TEXT("/Game")); } FPackage* Package = FPackage::FindOrCreatePackage(FName(*PackageName)); Package->ExclusiveSize += Size; } } TMap PackageToClassMap; // Combine with dependency information from asset registry. { FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray AssetData; AssetRegistryModule.Get().GetAllAssets(AssetData, true); TSet PackageNames; for (int i = 0; i < AssetData.Num(); i++) { PackageNames.Add(AssetData[i].PackageName); PackageToClassMap.Add(AssetData[i].PackageName, AssetData[i].AssetClass); } for (const auto& PackageName: PackageNames) { TArray DependencyArray; AssetRegistryModule.Get().GetDependencies(PackageName, DependencyArray); FPackage* Package = FPackage::FindOrCreatePackage(PackageName); for (const auto& DependencyName: DependencyArray) { // exclude '/Script/' as it clutters up things significantly. if (!DependencyName.ToString().StartsWith(TEXT("/Script/"))) { FPackage* Dependency = FPackage::FindOrCreatePackage(DependencyName); Package->DirectlyReferencing.Add(Dependency); Dependency->DirectlyReferencedBy.Add(Package); } } } // 2 passes are required to deal with cycles. for (const auto& PackageName: PackageNames) { FPackage* Package = FPackage::FindOrCreatePackage(PackageName); Package->RecurseUpdateReferences(); } for (const auto& PackageName: PackageNames) { FPackage* Package = FPackage::FindOrCreatePackage(PackageName); Package->ResetUpdateHelper(); } for (const auto& PackageName: PackageNames) { FPackage* Package = FPackage::FindOrCreatePackage(PackageName); Package->RecurseUpdateReferences(); } } // Update inclusive size, asset class, and export to CSV, JSON, and GDF { TSharedPtr JsonRootObject = MakeShareable(new FJsonObject); TArray> JsonPackages; TArray AllPackages; FPackage::GetAllPackages(AllPackages); for (auto Package: AllPackages) { Package->UpdateInclusiveSize(); JsonPackages.Add(MakeShareable(new FJsonValueObject(Package->ToJsonObject()))); } JsonRootObject->SetArrayField(TEXT("Packages"), JsonPackages); FString JsonOutputString; TSharedRef>> JsonWriter = TJsonWriterFactory>::Create(&JsonOutputString); FJsonSerializer::Serialize(JsonRootObject.ToSharedRef(), JsonWriter); FArchive* JsonFileWriter = IFileManager::Get().CreateFileWriter(*(FString(OutputFilenameBase) + TEXT(".json"))); if (JsonFileWriter) { JsonFileWriter->Logf(TEXT("%s"), *JsonOutputString); JsonFileWriter->Close(); delete JsonFileWriter; } FArchive* CSVFileWriter = IFileManager::Get().CreateFileWriter(*(FString(OutputFilenameBase) + TEXT(".csv"))); if (CSVFileWriter) { CSVFileWriter->Logf(TEXT("class,name,inclusive,exclusive")); for (auto Package: AllPackages) { FName ClassName = PackageToClassMap.FindRef(Package->Name); CSVFileWriter->Logf(TEXT("%s,%s,%i,%i"), *ClassName.ToString(), *Package->Name.ToString(), Package->InclusiveSize, Package->ExclusiveSize); } CSVFileWriter->Close(); delete CSVFileWriter; CSVFileWriter = NULL; } FArchive* GDFFileWriter = IFileManager::Get().CreateFileWriter(*(FString(OutputFilenameBase) + TEXT(".gdf"))); if (GDFFileWriter) { GDFFileWriter->Logf(TEXT("nodedef> name VARCHAR,label VARCHAR,inclusive DOUBLE,exclusive DOUBLE")); GDFFileWriter->Logf(TEXT("0,root,0,0")); for (auto Package: AllPackages) { GDFFileWriter->Logf(TEXT("%i,%s,%i,%i"), Package->Id, *Package->Name.ToString(), Package->InclusiveSize, Package->ExclusiveSize); } GDFFileWriter->Logf(TEXT("edgedef> node1 VARCHAR,node2 VARCHAR")); // fake root to ensure spanning tree for (auto Package: AllPackages) { GDFFileWriter->Logf(TEXT("0,%i"), Package->Id); } for (auto Package: AllPackages) { for (auto ReferencedPackage: Package->DirectlyReferencing) { GDFFileWriter->Logf(TEXT("%i,%i"), Package->Id, ReferencedPackage->Id); } } GDFFileWriter->Close(); delete GDFFileWriter; GDFFileWriter = NULL; } } return true; } else { return false; } } UExportPakDependenciesCommandlet::UExportPakDependenciesCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } int32 UExportPakDependenciesCommandlet::Main(const FString& Params) { FString PakFilename; FString ExportDependencyFilename; if (!FParse::Value(FCommandLine::Get(), TEXT("PakFile="), PakFilename) || !FParse::Value(FCommandLine::Get(), TEXT("Output="), ExportDependencyFilename)) { UE_LOG(LogPakFile, Error, TEXT("Incorrect arguments. Expected: -PakFile= -Output= [-Signed]")); return false; } bool bSigned = FParse::Param(FCommandLine::Get(), TEXT("signed")); return ExportDependencies(*PakFilename, FApp::GetProjectName(), *ExportDependencyFilename, bSigned) ? 0 : 1; }