Skip to content

UE5 Fix References Tools

经过对源码的大量搜索研究,发现UE5内部是通过直接操作UAsset二进制文件来修改引用缺失,丢失问题的。

但这个工具是UE5 Internal 标记的,C++ 也无法直接调用它,只能把它复制出来。它就是 AssetHeaderPatcher

这种级别的补丁,修复操作还是挺疯狂的,毕竟需要直接操作二进制,但UE5比较是大佬级的项目这种操作不在话下,操作起二进制就和我编辑文本文档一样简单。

(我不喜欢.uasset这种格式,它太难了,而且版本控制特别麻烦,每次小小的改动,都会提交一个全新的版本,非常占用空间。)

场景

日常开发中,需要在不同项目里复制资源文件。通常回使用 UE 自带的资源迁移工具,但必须打开UE编辑器才能进行。

但有时候,不希望打开UE编辑器,但又需要复制资源文件,可能会导致引用路径错误,这个时候就需要用到这个工具。

基础概念

UE 通过FLinkerLoad 把 .uasset文件加载到内存, 最终得到 UPackage 对象。

任何支持序列化的对象,都可以通过FArchive接口,通过重载 << 运算符,实现各自数据的写入和读取操作。<< 在UE里是一个双向读写操作符。

.uasset文件格式

其实 可以近似地理解为 UPackage 的构成。

FAssetHeaderPatcher 完整地列出了它全部组成部分。Summary 记录了文件的总大小,以及各个部分的偏移和大小,还有各个部分的名字,它是.uasset文件的头部信息。知道了这些信息,就可以进行修改和重写,从而修复引用缺失,丢失等问题。

cpp
FAssetHeaderPatcher::EResult FAssetHeaderPatcherInner::PatchHeader_WriteDestinationFile()
{
	// Serialize modified sections and reconstruct the file	
	// Original offsets and sizes of any sections that will be patched
	//	  Tag											Offset									Size												bRequired
	const FSectionData SourceSections[] = {
		{ EPatchedSection::Summary,						0,										HeaderInformation.SummarySize,						true	},
		{ EPatchedSection::NameTable,					Summary.NameOffset,						HeaderInformation.NameTableSize,					true	},
		{ EPatchedSection::SoftPathTable,				Summary.SoftObjectPathsOffset,			HeaderInformation.SoftObjectPathListSize,			false	},
		{ EPatchedSection::GatherableTextDataTable,		Summary.GatherableTextDataOffset,		HeaderInformation.GatherableTextDataSize,			false	},
		{ EPatchedSection::ImportTable,					Summary.ImportOffset,					HeaderInformation.ImportTableSize,					true	},
		{ EPatchedSection::ExportTable,					Summary.ExportOffset,					HeaderInformation.ExportTableSize,					true	},
		{ EPatchedSection::SoftPackageReferencesTable,	Summary.SoftPackageReferencesOffset,	HeaderInformation.SoftPackageReferencesListSize,	false	},
		{ EPatchedSection::SearchableNamesMap,			Summary.SearchableNamesOffset,			HeaderInformation.SearchableNamesMapSize,			false	},
		{ EPatchedSection::ThumbnailTable,				Summary.ThumbnailTableOffset,			HeaderInformation.ThumbnailTableSize,				false	},
		{ EPatchedSection::AssetRegistryData,			Summary.AssetRegistryDataOffset,		AssetRegistryData.SectionSize,						true	},
	};

	const int32 SourceTotalHeaderSize = Summary.TotalHeaderSize;

操作步骤

A 文件 在 A项目里引用了 A1文件。此时,把A复制到了B项目,A1是缺失的,B项目对应位置是B1.

这就需要用 Patcher 把 A1 文件的引用路径修正成B1文件的引用路径。

理论上,如果 A1 和 B1 是同名,甚至可以写个自动修复工具。我这里需要开发者自己选择对应的类:

把A1,B1路径映射设置到PackageRenames里,具体参考AssetHeaderPatcher 内部提供的TestCacases

cpp

bool UYMythEdTools::ReplaceAssetReference(const FString& AssetToFixPath, const FString& InvalidRefPath, const FString& NewRefPath)
{
//省略非核心代码

// 设置重定向上下文
	FAssetHeaderPatcher::FContext Context(PackageRenames, false);
	FCoreRedirects::AddRedirectList(Context.Redirects, TEXT("Asset Header Patcher Tests"));

//省略非核心代码

// 创建并执行 AssetHeaderPatcher
	FAssetHeaderPatcher Patcher;
	FAssetHeaderPatcher::EResult Result = Patcher.DoPatch(SrcFilename, TempFilePath, Context);

    auto DoCleanup = [TempFilePath, Context]
	{
		// 清理临时文件
		IFileManager::Get().Delete(*TempFilePath);
		// 清理重定向
		FCoreRedirects::RemoveRedirectList(Context.Redirects, TEXT("Asset Header Patcher Tests"));
	};

	if (Result != FAssetHeaderPatcher::EResult::Success)
	{
		DoCleanup();
		UE_LOG(LogTemp, Error, TEXT("Failed to replace asset reference. Asset: %s, Error: %s"), *SrcFilename, *LexToString(Result));
		return false;
	}

完整代码

https://github.com/kisspread/RefsFixedUp

references