// Copyright Epic Games, Inc. All Rights Reserved. #include "Enumeration/PatchDataEnumeration.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Common/FileSystem.h" #include "Data/ChunkData.h" #include "BuildPatchManifest.h" #include "BuildPatchUtil.h" DECLARE_LOG_CATEGORY_CLASS(LogDataEnumeration, Log, All); namespace EnumerationHelpers { template< typename DataType > FString ToHexString(const DataType& DataVal) { const void* AsBuffer = &DataVal; return BytesToHex(static_cast(AsBuffer), sizeof(DataType)); } bool IsChunkDbData(FArchive& Archive) { using namespace BuildPatchServices; const int64 ArPos = Archive.Tell(); FChunkDatabaseHeader ChunkDbHeader; Archive << ChunkDbHeader; bool bIsChunkDb = ChunkDbHeader.Version > 0; Archive.Seek(ArPos); return bIsChunkDb; } TUniquePtr LoadManifest(FArchive& Archive) { TUniquePtr Manifest(new FBuildPatchAppManifest()); TArray FileData; Archive.Seek(0); FileData.AddUninitialized(Archive.TotalSize()); Archive.Serialize(FileData.GetData(), Archive.TotalSize()); if (Archive.IsError() || !Manifest->DeserializeFromData(FileData)) { Manifest.Reset(); } return Manifest; } void AppendManifestDataList(FBuildPatchAppManifest& Manifest, TArray& OutFiles, TSet& DeDupeSet, bool bIncludeSizes) { TArray DataList; Manifest.GetDataList(DataList); UE_LOG(LogDataEnumeration, Verbose, TEXT("Data file list:-")); for (FGuid& DataGuid : DataList) { bool bAlreadyInSet = false; DeDupeSet.Add(DataGuid, &bAlreadyInSet); if (!bAlreadyInSet) { FString OutputLine = FBuildPatchUtils::GetDataFilename(Manifest, DataGuid); if (bIncludeSizes) { uint64 FileSize = Manifest.GetDataSize(DataGuid); OutputLine += FString::Printf(TEXT("\t%" UINT64_FMT), FileSize); } UE_LOG(LogDataEnumeration, Verbose, TEXT("%s"), *OutputLine); OutFiles.Add(MoveTemp(OutputLine)); } } } } namespace BuildPatchServices { class FPatchDataEnumeration : public IPatchDataEnumeration { public: FPatchDataEnumeration(const FPatchDataEnumerationConfiguration& InConfiguration); ~FPatchDataEnumeration(); // IPatchDataEnumeration interface begin. virtual bool Run() override; virtual bool Run(TArray& OutFiles) override; // IPatchDataEnumeration interface end. private: bool RunInternal(FArchive& Archive, TArray& OutFiles); bool EnumerateManifestData(FArchive& Archive, TArray& OutFiles); bool EnumerateChunkDbData(FArchive& Archive, TArray& OutFiles); private: const FPatchDataEnumerationConfiguration Configuration; const FString CloudDir; TUniquePtr FileSystem; }; FPatchDataEnumeration::FPatchDataEnumeration(const FPatchDataEnumerationConfiguration& InConfiguration) : Configuration(InConfiguration) , CloudDir(FPaths::GetPath(Configuration.InputFile)) , FileSystem(FFileSystemFactory::Create()) { } FPatchDataEnumeration::~FPatchDataEnumeration() { } bool FPatchDataEnumeration::Run() { TArray FullOutputList; bool bSuccess = Run(FullOutputList); if (bSuccess) { FString FullOutput = FString::Join(FullOutputList, TEXT("\r\n")); if (FFileHelper::SaveStringToFile(FullOutput, *Configuration.OutputFile)) { UE_LOG(LogDataEnumeration, Log, TEXT("Saved out to %s"), *Configuration.OutputFile); bSuccess = true; } else { UE_LOG(LogDataEnumeration, Error, TEXT("Failed to save output %s"), *Configuration.OutputFile); bSuccess = false; } } return bSuccess; } bool FPatchDataEnumeration::Run(TArray& FullOutputList) { bool bSuccess = false; TUniquePtr File = FileSystem->CreateFileReader(*Configuration.InputFile); if (File.IsValid()) { bSuccess = RunInternal(*File, FullOutputList); } return bSuccess; } bool FPatchDataEnumeration::RunInternal(FArchive& Archive, TArray& OutFiles) { bool bEnumerationOk = false; if (EnumerationHelpers::IsChunkDbData(Archive)) { bEnumerationOk = EnumerateChunkDbData(Archive, OutFiles); } else { bEnumerationOk = EnumerateManifestData(Archive, OutFiles); } return bEnumerationOk; } bool FPatchDataEnumeration::EnumerateManifestData(FArchive& Archive, TArray& OutFiles) { using namespace BuildPatchServices; bool bSuccess = false; TUniquePtr Manifest = EnumerationHelpers::LoadManifest(Archive); if (Manifest.IsValid()) { bSuccess = true; TArray> DeltaFiles; const FString DeltaOptimisationsRoot = CloudDir / FBuildPatchUtils::GetChunkDeltaDirectory(*Manifest.Get()); TArray DeltaOptimisationFiles; FileSystem->FindFilesRecursively(DeltaOptimisationFiles, *DeltaOptimisationsRoot); for (const FString& DeltaOptimisationFile : DeltaOptimisationFiles) { TUniquePtr File = FileSystem->CreateFileReader(*DeltaOptimisationFile); if (File.IsValid()) { TUniquePtr& DeltaFile = DeltaFiles.Add_GetRef(EnumerationHelpers::LoadManifest(*File.Get())); if (DeltaFile.IsValid()) { FString OutputLine = DeltaOptimisationFile.RightChop(CloudDir.Len() + 1); if (Configuration.bIncludeSizes) { uint64 FileSize = File->TotalSize(); OutputLine += FString::Printf(TEXT("\t%" UINT64_FMT), FileSize); } UE_LOG(LogDataEnumeration, Verbose, TEXT("%s"), *OutputLine); OutFiles.Add(MoveTemp(OutputLine)); } else { UE_LOG(LogDataEnumeration, Error, TEXT("Failed to deserialise delta %s"), *DeltaOptimisationFile); bSuccess = false; } } else { UE_LOG(LogDataEnumeration, Error, TEXT("Failed to load delta %s"), *DeltaOptimisationFile); bSuccess = false; } } if (bSuccess) { TSet VisitedDataSet; EnumerationHelpers::AppendManifestDataList(*Manifest.Get(), OutFiles, VisitedDataSet, Configuration.bIncludeSizes); for (const TUniquePtr& DeltaFile : DeltaFiles) { EnumerationHelpers::AppendManifestDataList(*DeltaFile.Get(), OutFiles, VisitedDataSet, Configuration.bIncludeSizes); } } } return bSuccess; } bool FPatchDataEnumeration::EnumerateChunkDbData(FArchive& Archive, TArray& OutFiles) { using namespace BuildPatchServices; bool bSuccess = false; FChunkDatabaseHeader ChunkDbHeader; Archive << ChunkDbHeader; if (!Archive.IsError()) { bSuccess = true; UE_LOG(LogDataEnumeration, Verbose, TEXT("Data file list:-")); for (const FChunkLocation& Location : ChunkDbHeader.Contents) { FChunkHeader ChunkHeader; Archive.Seek(Location.ByteStart); Archive << ChunkHeader; FString OutputLine = FString::Printf(TEXT("%s\t%s\t%s"), *Location.ChunkId.ToString(), *EnumerationHelpers::ToHexString(ChunkHeader.RollingHash), *ChunkHeader.SHAHash.ToString()); if (Configuration.bIncludeSizes) { OutputLine += FString::Printf(TEXT("\t%u"), Location.ByteSize); } UE_LOG(LogDataEnumeration, Verbose, TEXT("%s"), *OutputLine); OutFiles.Add(MoveTemp(OutputLine)); // If the header did not give valid info, mark as failed but continue. if (!ChunkHeader.Guid.IsValid()) { UE_LOG(LogDataEnumeration, Error, TEXT("Invalid chunk header for %s at %" UINT64_FMT), *Location.ChunkId.ToString(), Location.ByteStart); bSuccess = false; } // We treat a serialization error as critical, and stop reading. if (Archive.IsError()) { UE_LOG(LogDataEnumeration, Error, TEXT("Serialization error when reading at byte %" UINT64_FMT ". Aborting."), Location.ByteStart); bSuccess = false; break; } } } return bSuccess; } IPatchDataEnumeration* FPatchDataEnumerationFactory::Create(const FPatchDataEnumerationConfiguration& Configuration) { return new FPatchDataEnumeration(Configuration); } }