CD Code Digest Hub /GTA V · v2

parser 与元数据:.psc 反射、XML/RBF/PSO、.meta 加载

51 分钟阅读 11 个章节 1 张图

RAGE parser 是贯穿整个代码库的反射与序列化系统:游戏几乎所有可调数据(.meta 文件、tunables、ped/vehicle/weapon 配置、地图 ymap/ytyp 的元数据部分、存档的若干结构)都由 .psc 模式描述,经代码生成为 parStructure/parMember 元数据,再由统一的加载器读入 XML、RBF 或 PSO 二进制。本页解释该系统的核心类型、代码生成契约、三种文件格式的读写路径、版本兼容与 PSO 就地加载,并以 pedhealth.meta 为例端到端走一遍。

背景与相关页面#

相关源码文件#

区域关键文件(相对 src/dev_ng/rage/base/)
宏与入口src/parser/macros.h、src/parser/manager.h/.cpp、src/parser/codegenutils.h
反射核心src/parser/structure.h/.cpp、src/parser/member*.h/.cpp、src/parser/memberdata.h、src/parser/rtstructure.h、src/parser/extstructure.h
DOM 与访问者src/parser/tree.h、src/parser/treenode.h、src/parser/visitor*.h/.cpp
流格式(parsercore)src/parsercore/stream.h、streamxml.h/.cpp、streamrbf.cpp、settings.h、versioninfo.h/.cpp、attribute.h、element.h
PSOsrc/parser/psoconsts.h、psofile.h/.cpp、psofile_fixup.cpp、psofile_parser.cpp、psodata.h、psoresource.h、psoschema*.h、psobuilder*.h、psorscbuilder*.h、ifffile.h
代码生成器tools/cli/parCodeGen/parCodeGen.py、structtype.py、basetype.py、arraytype.py、template_runtimemetadata.py、atHash.py
示例src/dev_ng/game/Peds/HealthConfig.psc/.h/.cpp、src/dev_ng/game/scene/FileLoader.cpp

1. 核心概念#

概念类型职责证据
解析器单例parManager,宏 PARSER注册表(结构/枚举)、格式注册、LoadObject/SaveObject/LoadTree、设置src/dev_ng/rage/base/src/parser/macros.h:72-102、src/dev_ng/rage/base/src/parser/manager.h:53-478
结构描述parStructure一个 C++ 类的反射:名称哈希、基类与偏移、m_Size、标志、版本、成员数组、工厂/放置/析构委托、命名委托表(PreLoad/PostLoad/PreSet/PostSet/...)src/dev_ng/rage/base/src/parser/structure.h:30-60、src/dev_ng/rage/base/src/parser/structure.h:302-329
成员描述parMember 及子类 parMemberSimple/Vector/Matrix/String/Struct/Array/Enum/Map/Bitset每个成员一份静态 Data(名称哈希、偏移、类型、子类型、标志、访问者标志、额外属性)src/dev_ng/rage/base/src/parser/memberdata.h:26-36
运行时结构parRTStructure不经代码生成、运行时临时拼装的结构(绝对地址成员),用于脚本/工具src/dev_ng/rage/base/src/parser/rtstructure.h:34-60
中间 DOMparTree / parTreeNode(parElement + parAttribute)XML/RBF 读入后的树,LoadObject 先建树再映射到对象src/dev_ng/rage/base/src/parser/tree.h:18、src/dev_ng/rage/base/src/parser/treenode.h:35
流parStreamIn/parStreamOut,parStreamInXml、parStreamInRbf逐元素读写,支持回调式读取 ReadWithCallbackssrc/dev_ng/rage/base/src/parsercore/stream.h:104-220、src/dev_ng/rage/base/src/parsercore/streamxml.h:24-44
访问者parInstanceVisitor 派生:parInitVisitor(按 init= 初始化)、parBuildTreeVisitor、parXmlWriterVisitor、parBuildWidgetsVisitor(生成 bank 控件)、parDeepCopyVisitor、parDeleteAllVisitor、parValidateVisitor 等对任意已反射对象做通用遍历src/dev_ng/rage/base/src/parser/visitor.h:94、src/dev_ng/rage/base/src/parser/visitorinit.h:18、src/dev_ng/rage/base/src/parser/visitortree.h:34、src/dev_ng/rage/base/src/parser/visitorxml.h:19、src/dev_ng/rage/base/src/parser/visitorwidgets.h:19、src/dev_ng/rage/base/src/parser/visitorutils.h:310-375、src/dev_ng/rage/base/src/parser/visitorvalidate.h:35
PSOpsoFile、psoStruct、psoStructId、psoSchemaCatalog、psoResourceData二进制序列化实例 + 模式目录 + 签名,可就地加载src/dev_ng/rage/base/src/parser/psofile.h:191-300

1.1 成员类型#

parMemberType::Enum 列出所有可反射的原子/复合类型:bool、8/16/32/64 位整型、float/double/float16、旧数学库 Vector2/3/4、Matrix34/44,vectormath 的 Vec2V/Vec3V/Vec4V/Mat33V/Mat34V/Mat44V/ScalarV/BoolV/VecBoolV、size_t/ptrdiff_t,以及复合的 STRING/STRUCT/ARRAY/ENUM/BITSET/MAP src/dev_ng/rage/base/src/parser/memberdata.h:69-113。复合类型通过子类型区分具体容器:

类型子类型证据
ARRAYATARRAY、ATFIXEDARRAY、ATRANGEARRAY、POINTER、MEMBER(C 数组)、ATARRAY_32BIT_IDX、POINTER_WITH_COUNT(_8/_16BIT_IDX)、VIRTUALsrc/dev_ng/rage/base/src/parser/memberarraydata.h:27-36
MAPATMAP、ATBINARYMAPsrc/dev_ng/rage/base/src/parser/membermapdata.h:24-25
STRINGMEMBER、POINTER、CONST_STRING、ATSTRING、宽字符三种、ATNONFINALHASHSTRING、ATFINALHASHSTRING、ATHASHVALUE、ATPARTIALHASHVALUE、ATNSHASHSTRING、ATNSHASHVALUEsrc/dev_ng/rage/base/src/parser/memberstringdata.h:22-34
STRUCTSTRUCTURE(内嵌)、POINTER(拥有)、SIMPLE_POINTER、EXTERNAL_NAMED_POINTER(_USERNULL)(按名字引用外部对象)src/dev_ng/rage/base/src/parser/memberstructdata.h:21-25
ENUM / BITSET8/16/32 位;bitset 有固定宽度与 atBitSetsrc/dev_ng/rage/base/src/parser/memberenumdata.h:21-23、src/dev_ng/rage/base/src/parser/memberenumdata.h:107-110

成员标志 parMemberFlags(MEMBER_STATIC/READONLY/NO_INIT/ADD_GROUP_WIDGET 及 4 位数组对齐)与访问者掩码 parMemberVisitorFlags(VISIT_FILE/NET/SCRIPT/TOOL/BANK,游戏可从 VISIT_USER0=8 起自定义)控制某成员是否被某类访问者看到 src/dev_ng/rage/base/src/parser/memberdata.h:43-171。

1.2 名称与构建配置#

  • PARSER_ALL_METADATA_HAS_NAMES = __BANK || __DEV || __RESOURCECOMPILER || !__NO_OUTPUT || __TOOL:Final(无输出)构建中结构与成员只保留哈希不保留字符串,除非 .psc 中标记 preserveNames="true" src/dev_ng/rage/base/src/parser/macros.h:14-15。parStructure::m_NameHash 因此在两种构建中分别是 atLiteralHashString / atLiteralHashValue src/dev_ng/rage/base/src/parser/structure.h:304-308。
  • PARSER_USES_EXTERNAL_STRUCTURE_DEFNS(__DEV/资源编译器/工具)允许 parStructure 自身也可反射,支持 -parsercatalog 输出所有结构定义 src/dev_ng/rage/base/src/parser/macros.h:17-18、src/dev_ng/rage/base/src/parser/manager.cpp:71-84。
  • SPU 上 parser 整体编译为空 src/dev_ng/rage/base/src/parser/macros.h:26-36。

2. PAR_PARSABLE 与代码生成契约#

2.1 类内宏#

PAR_PARSABLE 向类注入:静态 parser_Register()、虚函数 parser_GetStructure()(用于多态:基类指针取到派生类结构)、parser_GetPointer()、静态 parser_Data(parStructureStaticData)与 parser_MemberOffsets[]。PAR_SIMPLE_PARSABLE 相同但 parser_GetStructure() 非虚,适用于无 vtable 的 POD 类,且不能作为可解析基类 src/dev_ng/rage/base/src/parser/macros.h:127-172。 parStructureStaticData 保存名称哈希、名称、构建后的 parStructure*、成员数据表、偏移表、成员名表、是否可继承、是否总保留名字 src/dev_ng/rage/base/src/parser/macros.h:215-229。

2.2 parCodeGen:.psc → _parser.h#

parCodeGen.py(打包为 tools_ng/bin/coding/python/parCodeGen.exe)读取 .psc(XML,根元素 ParserSchema),默认生成同名 <base>_parser.h 与 <base>_parser.xsd;当 .psc 要求生成类定义时还会生成 <base>.h/.cpp;若声明脚本导出则写入 game/script_headers 下的 .sch;只有当输入或生成器自身更新时才重新生成 src/dev_ng/rage/base/tools/cli/parCodeGen/parCodeGen.py:239-261、 src/dev_ng/rage/base/tools/cli/parCodeGen/parCodeGen.py:330-433。

生成的 _parser.h 由对应 .cpp 在顶部 #include,内容(由 template_runtimemetadata.py 输出):

  1. 每个成员一个静态 parMember???::Data 初始化器,及 void* const parser_<Mangled>__Members[](以 NULL 结尾)。
  2. parser_MemberOffsets[]:对每个成员取地址偏移——若 .psc 中的成员在 C++ 类中不存在,此处编译失败,这是 .psc 与 .h 一致性的编译期校验。
  3. 在 PARSER_ALL_METADATA_HAS_NAMES(或 preserveNames)时输出成员名表与结构名。
  4. parser_Data 初始化(NameHash 由生成器预先算好)。
  5. parser_Register():已注册则返回;否则 parCguStructure::CreateStructure(sizeof(T)),按 constructible 设置工厂/放置/析构函数(atPtrCreator、atPlacePtrCreator、parCguDestructor),设置 GetStructure 回调,按 .psc 的 onPreLoad/onPostLoad/onPreSet/onPostSet/onPreAddWidgets... 安装委托,最后 BuildStructureFromStaticData(newStructure, parser_Data, majorVersion, buildNumber)。
  6. 文件末尾为每个 autoregister 的结构生成全局 parCguAutoRegistrationNode autoreg_X(&X::parser_Register),可带 #if cond。

证据:src/dev_ng/rage/base/tools/cli/parCodeGen/template_runtimemetadata.py:210-300、src/dev_ng/rage/base/tools/cli/parCodeGen/template_runtimemetadata.py:395-406、src/dev_ng/rage/base/tools/cli/parCodeGen/template_runtimemetadata.py:520-540。

parCguAutoRegistrationNode 的构造函数把自身压入静态链表 sm_Head src/dev_ng/rage/base/src/parser/codegenutils.h:138-150, parManager::RegisterAutoStructs() 在初始化时遍历链表调用所有注册函数,然后清空链表;期间临时关闭“乱序注册/重复注册”警告 src/dev_ng/rage/base/src/parser/manager.cpp:180-215。

2.3 名称规则(XML 元素名 ⇄ 成员)#

  • 结构的解析器名默认是 C++ 类型名的 mangled 形式(:: → __),可用 name= 覆盖;NameHash = atLiteralStringHash(ParserName) src/dev_ng/rage/base/tools/cli/parCodeGen/structtype.py:150-165。
  • 成员名默认去掉 m_ 前缀(若剩余部分是合法 XML 名),可用 parName= 覆盖 src/dev_ng/rage/base/tools/cli/parCodeGen/basetype.py:372-385、src/dev_ng/rage/base/tools/cli/parCodeGen/basetype.py:405-422。
  • 数组元素子节点统一命名为 Item src/dev_ng/rage/base/tools/cli/parCodeGen/arraytype.py:20-24。
  • 同一 structdef 内两个成员名或名称哈希相同会在生成期报错 src/dev_ng/rage/base/tools/cli/parCodeGen/structtype.py:172-179。
  • structdef 支持的属性包括 type, version, base, constructible, autoregister, template, cond, rsc, preserveNames, generate, name, layoutHint, simple, platform, usedi 与各种 on* 回调 src/dev_ng/rage/base/tools/cli/parCodeGen/structtype.py:100-126、src/dev_ng/rage/base/tools/cli/parCodeGen/structtype.py:191-200。

2.4 头文件过期检测#

非 Final、非 Orbis 构建中 PAR_CHECK_OUT_OF_DATE_HEADER / PAR_HEADER_GUID 通过“引用一个只在新生成文件中定义的符号”把头文件与生成代码不同步变成链接错误 src/dev_ng/rage/base/src/parser/macros.h:194-213。


3. 初始化与注册#

INIT_PARSER → 若无单例则 new parManager → parSettings::CreateStandardSettings() → PARSER.Initialize(settings, true);Initialize 有引用计数,只有第一次调用会注册内建格式、执行自动注册与外部结构初始化;SHUTDOWN_PARSER 对称递减,归零时删除所有注册结构 src/dev_ng/rage/base/src/parser/macros.h:75-102、 src/dev_ng/rage/base/src/parser/manager.cpp:54-122。游戏最早的调用位于 CSystem 初始化中(注释说明 parser 必须先于 fwConfigManager 初始化) src/dev_ng/game/system/system.cpp:469-470;此后大量子系统在使用前各自再调用 INIT_PARSER(例如 game/system/control.cpp、src/dev_ng/game/control/leveldata.cpp:197、src/dev_ng/game/text/FontDef.cpp:62),依赖这一引用计数语义(由 rg INIT_PARSER 得出)。

内建格式注册:

格式头部检测读写实现证据
XMLparStreamInXml::IsValidXmlStartparStreamInXml / parStreamOutXmlsrc/dev_ng/rage/base/src/parser/manager.cpp:143-156
BINARY(RBF)前 4 字节 "RBF0"parStreamInRbf / parStreamOutRbfsrc/dev_ng/rage/base/src/parser/manager.cpp:159-171
PSO前 4 字节 "PSIN"仅用于识别;LoadTree/OpenInputStream 遇到 PSO 返回 NULL,需走 pso* 专用 APIsrc/dev_ng/rage/base/src/parser/manager.cpp:173-177、src/dev_ng/rage/base/src/parser/manager.cpp:320-323

格式枚举 parManager::Format { XML, BINARY, PSO, ... } 见 src/dev_ng/rage/base/src/parser/manager.h:243-248。


4. XML/RBF 读路径(PARSER.LoadObject)#

图证据:LoadObject 模板 src/dev_ng/rage/base/src/parser/manager.h:559-560;LoadTree src/dev_ng/rage/base/src/parser/manager.cpp:305-358;FindInputFormat src/dev_ng/rage/base/src/parser/manager.cpp:551-575;LoadFromStructure src/dev_ng/rage/base/src/parser/manager.cpp:643-725;ReadMembersFromTree src/dev_ng/rage/base/src/parser/structure.cpp:309-412。

关键行为:

  • 先建 DOM 再映射:XML/RBF 路径总是先把整个文件读成 parTree(使用 AutoUseTempMemory 临时堆),再写入对象,最后删除树。LoadTree 可在设置位允许时处理 XInclude 与“剔除其它平台数据”节点 src/dev_ng/rage/base/src/parser/manager.cpp:336-345。
  • 类型校验:根元素名必须是已注册结构,且目标结构 IsSubclassOf 文件结构;LOAD_UNKNOWN_TYPES 设置可放宽 src/dev_ng/rage/base/src/parser/manager.cpp:652-669。
  • 成员匹配:从最远的基类开始,对每个成员用 atLiteralStringHash(元素名) 与 GetNameHash() 比较;搜索从上一个匹配节点的下一个开始环形查找,因此文件中元素顺序与声明顺序一致时为线性复杂度,乱序也能加载;断言检查哈希碰撞 src/dev_ng/rage/base/src/parser/structure.cpp:347-382。
  • 缺失与多余数据:WARN_ON_MISSING_DATA / INITIALIZE_MISSING_DATA(用 parInitVisitor 按 .psc 的 init= 值初始化)/ WARN_ON_UNUSED_DATA src/dev_ng/rage/base/src/parser/structure.cpp:384-405、src/dev_ng/rage/base/src/parsercore/settings.h:41-70。
  • 回调与线程:PostLoad 受 TLS parStructure::sm_SkipPostloadCallbacks 控制——非主线程加载时可跳过、稍后补调 src/dev_ng/rage/base/src/parser/structure.h:292-293、src/dev_ng/rage/base/src/parser/structure.cpp:408-411。
  • 补丁文件已停用:LoadPatchFile 若在 content.xml 补丁列表中找到对应文件,会断言并忽略(注释说明 NG 不再应用补丁),旧的 REST 风格补丁代码被注释掉 src/dev_ng/rage/base/src/parser/manager.cpp:727-750。
  • LoadObjectPtr 由 parser 依据文件中的类型分配对象(要求传入 NULL 指针),InitAndLoadObject 先 InitObject 再加载 src/dev_ng/rage/base/src/parser/manager.h:513-607。

写路径 SaveObject → SaveFromStructure:同样先 BuildTreeNodeFromStructure 构建树再 SaveTree;保存到文件时会尝试版本控制签出 src/dev_ng/rage/base/src/parser/manager.h:638-640、src/dev_ng/rage/base/src/parser/manager.cpp:1075-1120。

4.1 XML 方言#

简单类型以 value 属性写出(如 <DefaultHealth value="200.0"/>) src/dev_ng/rage/base/src/parser/visitorxml.cpp:44;结构版本写为 v="major.minor" 属性;多态指针用 type= 属性指定实际类型 src/dev_ng/rage/base/src/parser/memberstruct.cpp:253。 PROCESS_SPECIAL_ATTRS 设置决定 v=、content=、value= 是否被特殊处理 src/dev_ng/rage/base/src/parsercore/settings.h:28-31。

4.2 RBF(Rockstar Binary Format)#

记录式二进制:记录 ID 的高 4 位为记录类型(CONTAINER/INT/BOOL_TRUE/BOOL_FALSE/FLOAT/VECTOR3/STRING,0xF 为特殊记录),低 12 位为名字表 ID;特殊 ID 0xFFFF 弹出、0xFFFE 扩展、0xFFFD 数据容器、0xFFFC 注释 src/dev_ng/rage/base/src/parsercore/streamrbf.cpp:27-56。数据容器支持 ASCII/UTF16/二进制与各类数值数组、矩阵编码(src/dev_ng/rage/base/src/parsercore/streamrbf.cpp:416-497)。

4.3 版本兼容#

parVersionInfo{u16 major, u16 minor}:文件版本 0.0 视为未版本化;主版本不同 → INCOMPATABLE;主版本相同则报告 FILE_IS_NEWER / CODE_IS_NEWER / EXACT_MATCH src/dev_ng/rage/base/src/parsercore/versioninfo.h:13-66、src/dev_ng/rage/base/src/parsercore/versioninfo.cpp:47-60。 ReadMembersFromTree 仅在结构主版本 > 0 时检查 v 属性:次版本差异只按 WARN_ON_MINOR_VERSION_MISMATCH 警告,主版本不兼容则断言并跳过整个结构 src/dev_ng/rage/base/src/parser/structure.cpp:277-323。除此之外的兼容手段是“按名称匹配 + 缺失成员初始化 + 多余元素忽略”。仓库中未找到名为 parRestructure 的类型(rg parRestructure 在 parser/parsercore 中无结果)。


5. PSO(Parser Serialized Object)#

5.1 文件结构#

PSO 是 IFF 风格的块文件:每块 8 字节头 {u32 magic(BE), u32 size(BE)} src/dev_ng/rage/base/src/parser/ifffile.h:18-28。块类型:

魔数内容
PSIN序列化实例数据(必须位于文件开头,用于格式识别)
PMAP结构数组映射表(每个结构数组的类型、偏移、大小,及根对象 ID)
PSCH模式目录(结构与枚举的布局描述)
PSIG结构签名
STRF / STRS / STREFinal 字符串 / 调试字符串 / 加密的调试字符串
CHKS校验和

证据:src/dev_ng/rage/base/src/parser/psoconsts.h:52-61。资源化(放入 RPF 的 pgBase 资源)形式为 psoResourceData,标识 'PRD0',记录创建平台(决定端序与指针宽度)、校验和、根对象 ID、结构/枚举模式表与结构数组表,RESOURCE_VERSION = 2;ENCRYPT_NONFINAL_STRINGS 标志表示非 Final 字符串被加密(存在一个名为 NonfinalStringKey 的非 Final 密钥常量,本页不记录其值) src/dev_ng/rage/base/src/parser/psoresource.h:26-88。

对象引用不用指针而用 psoStructId:12 位结构数组表索引 + 20 位字节偏移(OBJECT_IDENTIFIER_*_BITS),表索引存储时加 1,使 0 表示空 src/dev_ng/rage/base/src/parser/psoconsts.h:43-44、src/dev_ng/rage/base/src/parser/psodata.h:245-251。类型 ID 空间:0–255 为 POD 类型、256–3327 为文件内匿名类型、3328–3583 为命名空间哈希串、3584–4095 保留,≥4096 为类型名的字面哈希(因此类型名哈希若 < 4096 不被允许) src/dev_ng/rage/base/src/parser/psoconsts.h:21-37。

5.2 加载 API 与就地加载#

  • psoLoadFile / psoLoadFileFromBuffer / psoLoadResource / psoInitFromResource 返回 psoFile*;psoLoadAction 决定是否在加载时与运行时结构比较(PSOLOAD_PREP_FOR_PARSER_LOADING)或直接假定全部匹配(PSOLOAD_ASSUME_IN_PLACE_LOADING);psoLoadFileStatus 枚举报告缺块、校验和缺失/不匹配、大小不符等失败 src/dev_ng/rage/base/src/parser/psofile.h:33-77、src/dev_ng/rage/base/src/parser/psofile.cpp:486-519。
  • psoFile::InitFlags:NO_BYTE_SWAP_DATA、NO_INIT_HASH_STRING、IGNORE_CHECKSUM、REQUIRE_CHECKSUM src/dev_ng/rage/base/src/parser/psofile.h:204-210。
  • 签名匹配:CompareSchemasWithRuntime 对文件中每个非匿名结构模式,找到同名哈希的运行时 parStructure,用 psoBuildSchemaFromStructure 从运行时结构构建临时模式并计算签名(缓存为结构的额外属性 psosig);签名一致则给该模式设 FLAG_USE_FAST_LOADING,含虚函数的再设 FLAG_NEEDS_VPTR_FIXUP。不可构造或标记 NOT_IN_PLACE_LOADABLE 的结构签名记为 0(永不快速加载) src/dev_ng/rage/base/src/parser/psofile.cpp:608-730。
  • psoLoadObjectInPlace(file, inPlaceHeap, notInPlaceHeap):所有模式都匹配时做就地修复——PSO_DONT_ALLOCATE 直接在 PSO 缓冲中把 psoStructId 改回真实指针(DirectFixupMatchingPsoContents),否则复制到指定堆再修复(只支持 IFF 文件,不支持资源化 PSO);不匹配时退化为逐对象构造(notInPlaceHeap),若其为 PSO_DONT_ALLOCATE 则失败 src/dev_ng/rage/base/src/parser/psofile.h:145-187、src/dev_ng/rage/base/src/parser/psofile.cpp:922-990。
  • 也可走 psoLoadObject(file, obj) → psoLoadFromStructure,与 XML 路径一样逐成员写入现有对象 src/dev_ng/rage/base/src/parser/psofile.h:90-128、src/dev_ng/rage/base/src/parser/psofile.h:363-371。
  • 32/64 位:PSO_FORCE_32BIT = 0,PSO_USING_NATIVE_BIT_SIZE 宏用于屏蔽非原生位宽下不可用的 API src/dev_ng/rage/base/src/parser/macros.h:20-24。

PSO 的构建代码位于 psobuilder*、psorscbuilder*、psoschemabuilder*(推断:离线由资产流水线调用这些构建器把 XML 转成 PSO,本页未追踪具体工具入口,见 24-toolchain/asset-pipeline.md);psoRscBuilder 也在运行时用于计算签名(见上)。

5.3 framework 侧调用(简述)#

fwPsoStoreLoader::Load 用前 4 字节是否为 PSIN 判断 PSO(注释自称“HACK”),PSO 走 LoadPsoFile(psoLoadFileFromBuffer + psoLoadObjectInPlace,布局不匹配时按 WARN_/ASSERT_ON_LAYOUT_MISMATCH 或 -psoMismatchIsFatal 处理),否则若允许 XML 则包装成 memory: 设备文件后走 PARSER.CreateAndLoadAnyType src/dev_ng/rage/framework/src/fwscene/stores/psostore.cpp:145-175、 src/dev_ng/rage/framework/src/fwscene/stores/psostore.cpp:250-350、 src/dev_ng/rage/framework/src/fwscene/stores/psostore.cpp:454-485。 fwMetaDataStore 默认开启 LOAD_XML_FILES | LOAD_IN_PLACE | WARN_ON_LAYOUT_MISMATCH src/dev_ng/rage/framework/src/fwsys/metadatastore.cpp:32-35。详见 04-rage-framework/framework-systems.md。


6. 端到端示例:pedhealth.meta 与 CHealthConfigInfo#

1) 模式:game/Peds/HealthConfig.psc 定义两个结构。CHealthConfigInfo 有 atHashString 类型的 m_Name(ui_key="true")、若干带 init/min/max 的 float 阈值与两个 bool;CHealthConfigInfoManager 有 atArray<CHealthConfigInfo> m_aHealthConfig 和一个 policy="external_named" 的指针 m_DefaultSet,其 toString/fromString 指向管理器静态函数 src/dev_ng/game/Peds/HealthConfig.psc:4-24。

2) C++ 类:两类均以 PAR_SIMPLE_PARSABLE 声明(无虚函数),成员与 .psc 一一对应;m_Name 的 C++ 类型是 atHashWithStringNotFinal(Final 中只剩哈希,GetName() 仅非 Final 可用) src/dev_ng/game/Peds/HealthConfig.h:18-61、src/dev_ng/game/Peds/HealthConfig.h:63-124。 GetInfoFromName(name) = GetInfo(atStringHash(name)),线性遍历数组比较哈希,找不到返回 m_DefaultSet——这正是 external_named 指针在加载时把 XML 中的名字解析为对象指针所用的函数 src/dev_ng/game/Peds/HealthConfig.h:77-98。

3) 生成代码:HealthConfig.cpp 在类头之后 #include "HealthConfig_parser.h"(由 parCodeGen 生成,不在仓库中),其中包含两结构的 parser_Data、偏移表与 parser_Register,以及自动注册节点 src/dev_ng/game/Peds/HealthConfig.cpp:12-13。

4) 加载触发:CFileLoader::LoadHealthConfigFile 遍历 CDataFileMgr::PED_HEALTH_FILE 类型的数据文件条目(来自 content/datafile 配置)并调用 CHealthConfigInfoManager::Init(filename) src/dev_ng/game/scene/FileLoader.cpp:1501-1517; Load 断言尚未加载后执行 PARSER.LoadObject(pFileName, "meta", m_HealthConfigManagerInstance) src/dev_ng/game/Peds/HealthConfig.cpp:47-57。于是第 4 节的流程运行:根元素应为 <CHealthConfigInfoManager>,子元素 <aHealthConfig> 下每个 <Item> 是一个 CHealthConfigInfo,其字段为 <Name>、<DefaultHealth value="..."/> 等(名称规则见 2.3);<DefaultSet> 文本经 GetInfoFromName 解析。(XML 结构为依据名称规则推出,未在仓库中找到 pedhealth.meta 数据文件本身。)

5) 调试与保存:__BANK 下 InitWidgets 在 “A.I.” bank 中为每个条目调用 PARSER.AddWidgets(parBuildWidgetsVisitor 依据 min/max 等属性生成滑条),Save 用 PARSER.SaveObject("common:/data/pedhealth", "meta", ..., parManager::XML) 回写 src/dev_ng/game/Peds/HealthConfig.cpp:66-93。


7. 其它组件#

组件说明证据
parExternalStructureManager在支持外部结构定义的构建中管理从目录加载的结构定义(工具与调试用途)src/dev_ng/rage/base/src/parser/extstructure.h:32
restparserservices把已注册结构/单例通过 diag REST 服务暴露(Schema、GUID、单例读写)src/dev_ng/rage/base/src/parser/restparserservices.h:64-118
parSettings 其它标志RESIZE_EXISTING_ARRAYS、REALLOCATE_MISTYPED_POINTERS、ALWAYS_REALLOCATE、USE_TEMPORARY_HEAP、USE_SMALL_DATA_CHUNK_SIZE(1 KB 而非 16 KB 读缓冲)等src/dev_ng/rage/base/src/parsercore/settings.h:57-110
parser 诊断通道RAGE_DECLARE_CHANNEL(Parser) 与 parAssertf/parErrorf/...src/dev_ng/rage/base/src/parser/macros.h:233-246

约束与边界行为#

  • .psc 与 C++ 成员不一致 → parser_MemberOffsets 编译错误;头文件与生成文件不同步 → 链接错误(非 Final/非 Orbis)。
  • Final 构建中名字只剩哈希:调试输出、SaveObject、按名字查找结构可能不可用;需要字符串的结构必须 preserveNames="true"。
  • XML/RBF 加载需要临时堆中容纳整个 DOM;大文件会产生临时内存峰值。
  • 主版本不兼容时整个结构被跳过(只断言),对象保留原值。
  • PSO 就地加载只在所有非匿名模式签名匹配时发生;匿名结构不做签名验证(源码中留有 TODO)。不可构造或 NOT_IN_PLACE_LOADABLE 的结构强制走逐对象路径。
  • psoLoadObjectInPlace 以 PSO_DONT_ALLOCATE 就地修复后,psoFile 的部分 API 不再可用(指针已替换 ID)。
  • content.xml 中的补丁文件机制在本代码中被禁用。

未决问题 / 证据不足#

  • parCodeGen 由构建系统调用的具体命令行与输出目录未在本页核实(见 00-overview/build-and-config.md)。
  • psofile_fixup.cpp 中修复算法(vptr 修复、atArray 等容器的就地重建细节)与 psorscbuilder 的布局规则只做了存在性确认,未逐行分析。
  • 未确认运行时 .meta 在发行包中是 XML 还是 PSO(取决于资产流水线与 -loadreleasepsos 等参数,见 src/dev_ng/rage/framework/src/fwscene/stores/psostore.cpp:25-26)。