基于 UAssetAPI(UnrealBinaryReader / UnrealBinaryWriter / FName / INameMap 等)的 Unreal Engine AssetRegistry.bin 解析与回写库。
解析逻辑移植自 CUE4Parse 的 UE4/AssetRegistry 模块,但底层类全部替换为 UAssetAPI 提供的类型(仅 FGuid、FSHAHash、FMD5Hash 等 UAssetAPI 缺失的必要结构自行实现)。
- ✅ 解析 AssetRegistry.bin(完整
FAssetRegistryState:资产数组 + 依赖节点 + 包数据) - ✅ 快速枚举(
FPartialAssetRegistryState,只读核心字段,速度快) - ✅ 语义级回写(
FAssetRegistryWriter:状态 → bin,格式镜像读取器,UE/CUE4Parse 均可读回) - ✅ 手动建表(
FAssetData手动构造函数 +FAssetRegistryState.AddAsset) - ✅ Tags 完整保留(FStore 解析:AnsiString / WideString / Name / ExportPath / LocalizedText)
- ✅ FName 精确还原(含 number 后缀,如
ZenLogo_64) - ✅ 命令行演示工具(解析 / JSON 导出 / 简单版 JSON / 回写 / 手动构造测试)
- .NET 10(
net10.0) - UAssetAPI 1.1.0(NuGet)
<ProjectReference Include="..\UAssetRegistry\UAssetRegistry.csproj" />using UAssetRegistry;
// 解析
var state = AssetRegistryLoader.LoadFromFile("AssetRegistry.bin");
foreach (var asset in state.PreallocatedAssetDataBuffers)
{
Console.WriteLine($"[{asset.AssetClass}] {asset.ObjectPath}");
foreach (var (key, value) in asset.TagsAndValues)
Console.WriteLine($" {key} = {value}");
}
// 快速枚举(只读核心字段)
var partial = AssetRegistryLoader.LoadPartialFromFile("AssetRegistry.bin");
foreach (var p in partial.PreallocatedAssetDataBuffers)
Console.WriteLine(p.ObjectPath);
// 语义级回写
AssetRegistryLoader.SaveToFile(state, "output.bin");
// 手动构造新表
var newState = new FAssetRegistryState
{
Version = FAssetRegistryVersionType.ExternalActorToWorldIsEditorOnly,
FilterEditorOnly = true
};
newState.AddAsset(new FAssetData(
"/Game/X/BP_X", "/Game/X", "BP_X", "Blueprint",
new Dictionary<string, string> { ["ParentClass"] = "Class'/Script/Engine.Actor'" },
new[] { 0 }, EPackageFlags.PKG_Cooked));
AssetRegistryLoader.SaveToFile(newState, "manual.bin");用法: UAssetRegistry <AssetRegistry.bin 路径> [--top N] [--out <json文件>] [--partial] [--simple] [--write <回写bin路径>] [--manual-test]
| 参数 | 说明 |
|---|---|
--top N |
控制台显示条数(默认 10) |
--out <文件> |
JSON 输出路径(默认 <输入>.json) |
--partial |
快速枚举模式(只输出核心字段) |
--simple |
简单版 JSON(无 Tags/ChunkIDs,无 PackageFlags,Tags 仅保留父类信息),默认 <输入>.simple.json |
--write <bin路径> |
解析后语义级回写为新 bin |
--manual-test |
手动构造资产 → 写回 → 读回验证(无需输入文件) |
示例:
# 解析并导出完整 JSON
UAssetRegistry AssetRegistry.bin
# 简单版 JSON(仅核心字段 + 父类信息)
UAssetRegistry AssetRegistry.bin --simple
# 解析后回写
UAssetRegistry AssetRegistry.bin --write out.bin
# 手动构造测试
UAssetRegistry --manual-test把 UAssetRegistry 当 SDK 使用时,可以从零构造或在现有 bin 上增/改/删资产项,再写回。
using UAssetRegistry;
using UAssetAPI.UnrealTypes;
var state = new FAssetRegistryState
{
// 建议用 21(ExternalActorToWorldIsEditorOnly,与常见发布版一致)。
// 注意:不要用 LatestVersion(24) —— 该版本启用 16 字节对齐,当前 writer 不产生对齐填充。
Version = FAssetRegistryVersionType.ExternalActorToWorldIsEditorOnly,
FilterEditorOnly = true
};
state.AddAsset(new FAssetData(
packageName: "/Game/MyFolder/BP_Enemy", // 完整包路径
packagePath: "/Game/MyFolder", // 目录路径
assetName: "BP_Enemy", // 资产名
assetClass: "Blueprint", // 资产类型
tagsAndValues: new Dictionary<string, string>
{
["ParentClass"] = "Class'/Script/Engine.Character'",
["NativeParentClass"] = "/Script/CoreUObject.Class'/Script/Engine.Character'"
},
chunkIDs: new[] { 0 },
packageFlags: EPackageFlags.PKG_Cooked));
AssetRegistryLoader.SaveToFile(state, "new.bin"); // 写回 bin
byte[] bytes = AssetRegistryLoader.SaveToBytes(state); // 或取字节var state = AssetRegistryLoader.LoadFromFile("existing.bin");
state.AddAsset(new FAssetData(
"/Game/New/BP_New", "/Game/New", "BP_New", "Blueprint",
new Dictionary<string, string> { ["ParentClass"] = "Class'/Script/Engine.Actor'" }));
AssetRegistryLoader.SaveToFile(state, "existing_updated.bin");
// 可选:LoadFromFile 读回验证 —— 新项应出现在资产列表里
var back = AssetRegistryLoader.LoadFromFile("existing_updated.bin");
Console.WriteLine(back.PreallocatedAssetDataBuffers.Length); // 原数量 + 1var state = AssetRegistryLoader.LoadFromFile("existing.bin");
var target = state.PreallocatedAssetDataBuffers.First(a => a.ObjectPath == "/Game/X/BP_X.BP_X");
// 修改已有 tag(key 是 FName,需先找到原 key)
var parentKey = target.TagsAndValues.Keys.First(k => k.ToString() == "ParentClass");
target.TagsAndValues[parentKey] = "Class'/Script/Engine.Pawn'";
// 新增 tag(key 用 UAssetAPI.FName 的 dummy 形式,值写入时自动收集)
var map = new RegistryNameMap();
target.TagsAndValues[FName.DefineDummy(map, "MyNewTag")] = "Hello 世界";
AssetRegistryLoader.SaveToFile(state, "existing_updated.bin");var state = AssetRegistryLoader.LoadFromFile("existing.bin");
var list = state.PreallocatedAssetDataBuffers.ToList();
list.RemoveAll(a => a.ObjectPath == "/Game/X/BP_X.BP_X");
state.PreallocatedAssetDataBuffers = list.ToArray();
AssetRegistryLoader.SaveToFile(state, "existing_updated.bin");| 参数 | 含义 | 示例 |
|---|---|---|
packageName |
完整包路径(ObjectPath 的包部分) | /Game/MyFolder/BP_Enemy |
packagePath |
资产所在目录 | /Game/MyFolder |
assetName |
资产名 | BP_Enemy |
assetClass |
资产类型(写回时存为 FTopLevelAssetPath 的 AssetName) |
Blueprint / Texture2D / Material |
tagsAndValues |
tag 字典(key 属性名,value 字符串;ParentClass 等继承信息也放这里) |
["ParentClass"] = "Class'/Script/Engine.Character'" |
chunkIDs |
chunk 归属,通常 [0] |
new[] { 0 } |
packageFlags |
EPackageFlags 组合 |
EPackageFlags.PKG_Cooked |
- Version 不要用
LatestVersion(24):该版本启用 16 字节对齐,当前FAssetRegistryWriter不产生对齐填充,会导致读回错位。手动建表建议21(或与目标引擎一致的无对齐版本)。 - Tags 的 value 统一按 AnsiString 语义级编码,中文字符串(如
Hello 世界)可正常读写。 - FName 字符串含
_N时按普通字符原样保存(number=0);需真实 number 后缀请在解析出的资产上修改(读回的资产保留原始 number)。
| 类型 | 说明 |
|---|---|
AssetRegistryLoader |
统一入口:LoadFromBytes/File/Stream、LoadPartialFromBytes/File、SaveToBytes/Stream/File |
FAssetRegistryState |
完整解析结果:PreallocatedAssetDataBuffers(资产)、PreallocatedDependsNodeDataBuffers(依赖)、PreallocatedPackageDataBuffers(包数据);Version/FilterEditorOnly;AddAsset |
FPartialAssetRegistryState |
部分解析结果(仅 FPartialAssetData[]) |
FAssetData |
单资产:PackageName/PackagePath/AssetName/AssetClass/TagsAndValues/TaggedAssetBundles/ChunkIDs/PackageFlags/ObjectPath;含手动构造函数 |
FAssetIdentifier / FDependsNode / FAssetPackageData |
依赖关系与包数据 |
FAssetRegistryReader / FNameTableArchiveReader |
新版(FixedTags+)/ 旧版读取器 |
FAssetRegistryWriter |
语义级回写器 |
FAssetRegistryHeader / FAssetRegistryVersionType |
文件头与版本枚举(值对齐 UAssetAPI 的 FAssetRegistryVersion,并补全 UE 后续版本) |
RegistryNameMap |
INameMap 实现,使 UAssetAPI 的 FName 直接使用 AssetRegistry 内嵌 name 表 |
header (GUID + int32 version + int32 bFilterEditorOnly)
→ name batch(count + numStringBytes + hashVersion + hashes + 2B headers + name 数据)
→ 全局 FStore(magic + nums[11] + AnsiString/WideString 表 + Pairs + EndMagic)
→ FAssetData[](每个资产:FName 字段 + map 头(ulong) + bundles + ChunkIDs + PackageFlags)
→ dependencies(int64 sectionSize + 节点)
→ packages(int32 count + 包数据)
FAssetRegistryVersionType 前 18 个值与 UAssetAPI CustomVersions.FAssetRegistryVersion 一致;UAssetAPI 1.1.0 缺少 UE 后续版本(18~24),已在 FAssetRegistryVersionType 中补全。
FAssetRegistryWriter 重建 name 表与 FStore(tag 值统一编码为 AnsiString 类型)。输出为标准合法的 AssetRegistry.bin(读回后全部资产与 tags 逐字段一致),字节与原文件不同。若需字节级一致,需在解析时额外保留原始 FValueId 类型。
AssetRegistry.bin 无文件级/资产级校验和。存在的 magic 仅约束结构边界(header GUID、FStore EndMagic);FAssetPackageData 中的 CookedHash(MD5) / ChunkHashes(FIoHash) 是包内容哈希,且非所有文件都含包数据。
UAssetRegistry/
├── UAssetRegistry.csproj # net10.0,引用 UAssetAPI 1.1.0(Exe + 可引用)
├── Program.cs # 命令行演示工具
├── README.md
└── AssetRegistry/ # 库源码(命名空间 UAssetRegistry)
├── AssetRegistryLoader.cs
├── FAssetRegistryState.cs / FPartialAssetRegistryState.cs
├── FAssetRegistryReader.cs / FNameTableArchiveReader.cs / FAssetRegistryArchive.cs
├── FAssetRegistryWriter.cs
├── FAssetRegistryHeader.cs / FAssetRegistryVersion.cs
├── FAssetData.cs / FAssetIdentifier.cs / FDependsNode.cs / FAssetPackageData.cs
├── FAssetBundleData.cs / FAssetBundleEntry.cs
├── FStore.cs / FValueHandle.cs / FValueId.cs
├── FNumberedPair.cs / FNumberlessPair.cs
├── FNumberlessExportPath.cs / FAssetRegistryExportPath.cs
├── FPartialAssetData.cs
├── FTopLevelAssetPath.cs / FSoftObjectPath.cs / FGuid.cs / FSHAHash.cs / FMD5Hash.cs / FPackageFileVersion.cs
├── ELoadOrder.cs
└── RegistryNameMap.cs