Files
UnrealEngine/Engine/Source/Developer/AssetTools/Internal/AssetHeaderPatcher.h
2025-05-18 13:04:45 +08:00

227 lines
8.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Tasks/Task.h"
#include "Containers/ContainersFwd.h"
#include "UObject/CoreRedirects.h"
#include "UObject/CoreRedirects/CoreRedirectsContext.h"
/**
* Delegate called on when patch operation completes
* @param SrcFilePath Path of file being read for patching
* @param DstFilePath Path of file being written to after patching
*/
DECLARE_DELEGATE_TwoParams(FAssetHeaderPatcherCompletionDelegate, const FString& /*SrcFilePath*/, const FString& /*DstFilePath*/)
UE_INTERNAL struct ASSETTOOLS_API FAssetHeaderPatcher
{
static const EUnrealEngineObjectUE5Version MinimumSupportedUE5FileVersion = EUnrealEngineObjectUE5Version::ADD_SOFTOBJECTPATH_LIST;
enum class EResult
{
NotStarted,
Cancelled,
InProgress,
Success,
ErrorFailedToLoadSourceAsset,
ErrorFailedToDeserializeSourceAsset,
ErrorUnexpectedSectionOrder,
ErrorBadOffset,
ErrorUnkownSection,
ErrorFailedToOpenDestinationFile,
ErrorFailedToWriteToDestinationFile,
ErrorEmptyRequireSection,
};
UE_INTERNAL struct ASSETTOOLS_API FContext
{
FContext() = default;
/**
* Context used for patching. Contains all information for how object and package references
* will be changed as part of patching.
* *
* When bInGatherDependentPackages is true, the provided long package name (/Root/Folder/Package) to
* destination long package name mapping will be used to find any dependent packages that must also
* be patched due to internal references. The mapping provided in InSrcAndDstPackagePaths will be used
* to determine the filepath on disk to write when patching.
*
* @param InSrcAndDstPackagePaths Map of all long package names (/Root/Folder/Package) to be patched and to which new name they should be patched to.
* @param bInGatherDependentPackages If true (default), upon creating the context GatherDependentPackages() will be called.
**/
FContext(const TMap<FString, FString>& InSrcAndDstPackagePaths, const bool bInGatherDependentPackages = true);
/**
* Context used for patching. Contains all information for how object and package references
* will be changed as part of patching.
*
* When patching, package paths to patch will be deduced by the filepath mappings provided in InSrcAndDstFilePaths. All assets
* under InSrcRoot will be written as package paths under a mountpoint located at InSrcBaseDir.
*
* e.g. Path "C:/User/Repo/Project/Content/Skeletons/Player.uasset" -> "/InSrcRoot/Skeletons/Player" when InSrcBaseDir=C:/User/Repo/Project (/Content is assumed internally)
*
* @param InSrcRoot The root mount point for assets to be patched
* @param InDstRoot The new root mount point for patched assets to be placed under
* @param InSrcBaseDir Path to the directory holding the /Content/ directory for assets to patch
* @param InSrcAndDstFilePaths Map of filepaths for files to be patched and where to write the patched version to
* @param InMountPointReplacements Map of root mountpoints (name only, no "/" prefix or suffix) to replace when patching
**/
FContext(const FString& InSrcRoot, const FString& InDstRoot, const FString& InSrcBaseDir, const TMap<FString, FString>& InSrcAndDstFilePaths, const TMap<FString, FString>& InMountPointReplacements);
/*
* Returns the mapping of source long package names to destination package paths used when patching.
* This mapping may include more packages than initially supplied to the FContext
* if GatherDependentPackages has already been called.
* Note, this map can be invalidated by calls to GatherDependentPackages()
*/
const TMap<FString, FString>& GetLongPackagePathRemapping() const
{
return PackagePathRenameMap;
};
protected:
friend FAssetHeaderPatcher;
void AddVerseMounts();
void GatherDependentPackages();
void GenerateFilePathsFromPackagePaths();
void GeneratePackagePathsFromFilePaths(const FString& InSrcRoot, const FString& InDstRoot, const FString& InSrcBaseDir);
void GenerateAdditionalRemappings();
TArray<FString> VerseMountPoints;
TMap<FString, FString> PackagePathRenameMap;
TMap<FString, FString> FilePathRenameMap;
// Todo: Make TSet once FCoreRedirect GetTypeHash is implemented
TArray<FCoreRedirect> Redirects;
mutable FCoreRedirectsContext RedirectsContext;
// String mappings are only used for best-effort replacements. These will be error-prone
// and we should strive for more structured data formats to guard against errors here
TMap<FString, FString> StringReplacements;
TMap<FString, FString> StringMountReplacements;
};
FAssetHeaderPatcher() = default;
FAssetHeaderPatcher(FContext InContext) { SetContext(InContext); }
/*
* Resets the patcher state and sets a new patching context.
* It is an error to call while patching is already in progress.
*/
void SetContext(FContext InContext);
/*
* Schedules the reading of source files determined by the patcher context, as well as the writing of the patched versions
* of all source files read.
*
* @param InOutNumFilesToPatch Optional value to know how many files are expected to be read/written during patching
* @param InOutNumFilesPatched Optional value used to know how the patcher is progressing (useful for progress bars)
*/
UE::Tasks::FTask PatchAsync(int32* InOutNumFilesToPatch = nullptr, int32* InOutNumFilesPatched = nullptr);
UE::Tasks::FTask PatchAsync(int32* InOutNumFilesToPatch, int32* InOutNumFilesPatched, FAssetHeaderPatcherCompletionDelegate InOnSuccess, FAssetHeaderPatcherCompletionDelegate InOnError);
/*
* Returns the status of any inflight patching operations. In the case of multiple errors, the last seen error will be reported.
* Per file error status codes can be returned with GetErrorFiles().
*/
EResult GetPatchResult() const
{
return Status;
}
/*
* Returns source file -> destination mapping for all files that were patched successfully.
*/
TMap<FString, FString> GetPatchedFiles() const
{
if (IsPatching())
{
return TMap<FString, FString>();
}
return PatchedFiles;
}
/*
* Returns true if the patcher encountered errors (even if patching was cancelled)
*/
bool HasErrors()
{
FScopeLock Lock(&ErroredFilesLock);
return !!ErroredFiles.Num();
}
/*
* Returns a map of all files that had an error during patching with an error code to provide context as to the cause of the error.
*/
TMap<FString, EResult> GetErrorFiles()
{
FScopeLock Lock(&ErroredFilesLock);
return ErroredFiles;
}
/*
* Returns true if the patcher is still in theprocess of patching.
*/
bool IsPatching() const
{
return !PatchingTask.IsCompleted();
}
/*
* Cancels an in-flight patching operation. Patching work on individual files that has already started will run to completion
* however any files that have not started patching will be skipped. Even after cancelling, one must wait for the patcher to complete
* by waiting on the GetPatchingTask() explciitly or until IsPatching returns false.
*
* @return true if an in-flight patching operation was cancelled. If no patching operation is underway, returns false.
*/
bool CancelPatching()
{
if (!IsPatching())
{
return false;
}
bCancelled = true;
Status = EResult::Cancelled;
return true;
}
/*
* Returns the task for all patcher work underway. Waiting on this task will guarantee all patch work is completed.
*/
UE::Tasks::FTask GetPatchingTask() const
{
return PatchingTask;
}
/**
* Patches object and package references contained within InSrcAsset using the mapping provided to InContext. The
* patched asset will be written to InDstAsset.
*
* @param InSrcAsset Long package name (/Root/Folder/Package) to read in to be patched.
* @param InDstAsset Long package name (/Root/Folder/Package) where the patched package will be written to.
* @param InContext Context for how the patching will be performed. Contains all remapping information to the patcher.
* @return Success patching was successful and the InDstAsset package was written. Returns an error status otherwise.
**/
static EResult DoPatch(const FString& InSrcAsset, const FString& InDstAsset, const FContext& InContext);
private:
void Reset();
FContext Context;
TMap<FString, EResult> ErroredFiles;
FCriticalSection ErroredFilesLock;
TMap<FString, FString> PatchedFiles;
UE::Tasks::FTask PatchingTask;
EResult Status = EResult::NotStarted;
std::atomic<bool> bCancelled = false;
};
UE_INTERNAL ASSETTOOLS_API FString LexToString(FAssetHeaderPatcher::EResult InResult);