Files
2025-05-18 13:04:45 +08:00

255 lines
7.8 KiB
C++

// 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<const uint8*>(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<FBuildPatchAppManifest> LoadManifest(FArchive& Archive)
{
TUniquePtr<FBuildPatchAppManifest> Manifest(new FBuildPatchAppManifest());
TArray<uint8> 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<FString>& OutFiles, TSet<FGuid>& DeDupeSet, bool bIncludeSizes)
{
TArray<FGuid> 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<FString>& OutFiles) override;
// IPatchDataEnumeration interface end.
private:
bool RunInternal(FArchive& Archive, TArray<FString>& OutFiles);
bool EnumerateManifestData(FArchive& Archive, TArray<FString>& OutFiles);
bool EnumerateChunkDbData(FArchive& Archive, TArray<FString>& OutFiles);
private:
const FPatchDataEnumerationConfiguration Configuration;
const FString CloudDir;
TUniquePtr<IFileSystem> FileSystem;
};
FPatchDataEnumeration::FPatchDataEnumeration(const FPatchDataEnumerationConfiguration& InConfiguration)
: Configuration(InConfiguration)
, CloudDir(FPaths::GetPath(Configuration.InputFile))
, FileSystem(FFileSystemFactory::Create())
{
}
FPatchDataEnumeration::~FPatchDataEnumeration()
{
}
bool FPatchDataEnumeration::Run()
{
TArray<FString> 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<FString>& FullOutputList)
{
bool bSuccess = false;
TUniquePtr<FArchive> File = FileSystem->CreateFileReader(*Configuration.InputFile);
if (File.IsValid())
{
bSuccess = RunInternal(*File, FullOutputList);
}
return bSuccess;
}
bool FPatchDataEnumeration::RunInternal(FArchive& Archive, TArray<FString>& 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<FString>& OutFiles)
{
using namespace BuildPatchServices;
bool bSuccess = false;
TUniquePtr<FBuildPatchAppManifest> Manifest = EnumerationHelpers::LoadManifest(Archive);
if (Manifest.IsValid())
{
bSuccess = true;
TArray<TUniquePtr<FBuildPatchAppManifest>> DeltaFiles;
const FString DeltaOptimisationsRoot = CloudDir / FBuildPatchUtils::GetChunkDeltaDirectory(*Manifest.Get());
TArray<FString> DeltaOptimisationFiles;
FileSystem->FindFilesRecursively(DeltaOptimisationFiles, *DeltaOptimisationsRoot);
for (const FString& DeltaOptimisationFile : DeltaOptimisationFiles)
{
TUniquePtr<FArchive> File = FileSystem->CreateFileReader(*DeltaOptimisationFile);
if (File.IsValid())
{
TUniquePtr<FBuildPatchAppManifest>& 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<FGuid> VisitedDataSet;
EnumerationHelpers::AppendManifestDataList(*Manifest.Get(), OutFiles, VisitedDataSet, Configuration.bIncludeSizes);
for (const TUniquePtr<FBuildPatchAppManifest>& DeltaFile : DeltaFiles)
{
EnumerationHelpers::AppendManifestDataList(*DeltaFile.Get(), OutFiles, VisitedDataSet, Configuration.bIncludeSizes);
}
}
}
return bSuccess;
}
bool FPatchDataEnumeration::EnumerateChunkDbData(FArchive& Archive, TArray<FString>& 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);
}
}