paging 与资源:pgBase、datResource 重定位、资源构建与流式读取
RAGE 的“资源”(resource)是离线把整棵 C++ 对象图按目标平台内存布局序列化成一块或多块内存镜像的格式:运行时只需把这些块读入内存、按块表调整指针(fixup)、再用 placement new 调用每个对象的“资源构造函数”恢复 vtable 与运行时状态,无需逐字段反序列化。所有 .ydr/.ytd/.yft/.ybn/.ycd/.ymt/...(Win64;其他平台首字母不同)都是这种文件。本页说明资源文件头与块布局、datResource 的修复算法、pgBase 及其页表、pgRscBuilder 的构建与加载入口、pgStreamer 异步读取,以及 pgDictionary/pgRef/datOwner 等资源内指针类型。
背景与相关页面#
- RPF 包内资源条目如何记录资源信息(
fiPackEntry的datResourceInfo、版本)见 file-system-and-rpf.md。 - 资源内存从哪个堆分配(
MEMTYPE_RESOURCE_VIRTUAL/PHYSICAL、buddy 分配器、AllocateMap)见 01-runtime/memory-and-pools.md。 - 高层流式调度(
strStreamingEngine、模块注册、请求优先级、PlaceResource)见 04-rage-framework/streaming-engine.md;游戏侧见 05-world/game-streaming-and-scene-streamer.md。 - 资产流水线中谁调用资源编译器生成这些文件,见 24-toolchain/asset-pipeline.md。
- 具体资源类型(
rmcDrawable、grcTexture、fragType、phBound、crClipDictionary…)分别在 03-rage-graphics、09-physics、10-animation 等页面。
相关源码文件#
| 区域 | 文件(相对 src/dev_ng/rage/base/src/) |
|---|---|
| 资源头 / 块描述 | data/resourceheader.h/.cpp |
| 重定位 | data/resource.h/.cpp、paging/base.cpp(datResource 构造与 GetFixup) |
| 资源内指针与结构声明 | data/struct.h(datRef/datOwner/datTypeStruct/datSwapper)、paging/ref.h、paging/owner.h、paging/handle.h |
| 资源基类 | paging/base.h/.cpp、paging/base_spu.h、paging/knownrefpool_config.h |
| 构建与加载 | paging/rscbuilder.h/.cpp |
| 流式读取 | paging/streamer.h/.cpp、paging/streamer_internal.h |
| 字典 | paging/dictionary.h |
| 扩展名表 | rage/framework/src/fwsys/fileExts.h |
1. 资源文件格式#
1.1 文件头#
datResourceFileHeader 是 16 字节:u32 Magic = 0x37435352 ('RSC7')、int Version(应用定义;打包进 RPF 后只保留低 8 位)、datResourceInfo Info(8 字节,虚拟/物理两段的尺寸编码)。资源在构建时被安置在固定的“源地址空间”:虚拟段基址 0x50000000,物理段基址 0x60000000 src/dev_ng/rage/base/src/data/resourceheader.h:117-130。 IsValidResource 在 Win32 PC(工具侧)还接受字节交换后的魔数并就地翻转头部 src/dev_ng/rage/base/src/data/resource.cpp:108-127。
1.2 datResourceInfo:用 32 位描述一段的块序列#
每段(Virtual、Physical)用一个 32 位位域 Sizes 描述:LeafShift(4 位,基本块 = 叶尺寸 << LeafShift)、Head16Count(1)、Head8Count(2)、Head4Count(4)、Head2Count(6)、BaseCount(7)、HasTail2/4/8/16(各 1)、Version(4)。即一段内存由若干 16×/8×/4×/2×/1× 基本块加至多四个 1/2…1/16 尾块组成;8 位资源版本被拆成两段各 4 位 src/dev_ng/rage/base/src/data/resourceheader.h:47-113。
叶尺寸(leaf size):PS3 虚拟 4096 / 物理 5504,其它平台均为 8192;资源编译器按目标平台动态设置 src/dev_ng/rage/base/src/data/resourceheader.h:31-45、src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1152-1164。
datResourceInfo::GenerateMap 依次为虚拟段、物理段展开块表:从最大块开始,每类块按数量重复,块尺寸逐级减半;源地址从固定基址连续递增,目标地址待分配 src/dev_ng/rage/base/src/data/resourceheader.cpp:14-61。单次读取的最大块数 datResourceChunk::MAX_CHUNKS = 128 src/dev_ng/rage/base/src/data/resourceheader.h:133-143。
推断:这种“2 的幂块 + 尾块”设计使运行时分配可以直接映射到 buddy 分配器的节点尺寸(
pgRscBuilder::ComputeLeafSize在ENABLE_BUDDY_ALLOCATOR时调用sysMemBuddyAllocator::ComputeNodeSize,src/dev_ng/rage/base/src/paging/rscbuilder.cpp:450-466),从而避免大块连续内存需求。
1.3 磁盘布局#
SaveBuild 写出:16 字节头(大小、魔数、版本按目标平台字节序交换)+ 虚拟段与物理段连续做 raw deflate(deflateInit2(..., -MAX_WBITS, ...);64 位平台总是 zlib,32 位 Xenon 默认用 XCompress,-fastCompression 切换)后的数据;-uncompressedresources 时写未压缩数据。可选 AES 加密(新平台存在按文件选择密钥的机制;本页不记录算法与密钥)。版本号低 8 位不得为 0 src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1306-1500。放入 RPF 后,魔数不再保存,datResourceInfo 与版本单独记录在包条目中(见 file-system-and-rpf.md),这与头文件注释“not kept in an archive”一致 src/dev_ng/rage/base/src/data/resourceheader.h:123-126。
1.4 扩展名#
扩展名首字母是平台码:代码里以 '#' 占位,由 pgRscBuilder::ConstructName 替换为 g_sysPlatform(路径中的 # 也会被替换) src/dev_ng/rage/base/src/paging/rscbuilder.cpp:75-110。framework 中定义了各类型的模式与每平台扩展名,例如 Win64 ydr/ytd/ymt、Durango ddr/dtd/dmt、Orbis odr/otd/omt、Xenon x??、PS3 c??、平台无关 idr/itd src/dev_ng/rage/framework/src/fwsys/fileExts.h:79-271、src/dev_ng/rage/framework/src/fwsys/fileExts.h:293-328。
| 模式 | 类型 | 版本常量 |
|---|---|---|
#dr / #dd | 可绘制体 / 可绘制体字典 | rmcDrawable::RORC_VERSION + 18 |
#ft | 碎片(fragType) | rvFragObject |
#td | 纹理字典 | grcTexture*::RORC_VERSION |
#cd | 动画剪辑字典 | crClipDictionary::RORC_VERSION |
#bn / #bd | 物理包围体 / 包围体字典 | phBound::RORC_VERSION |
#ld | 布料 | phVerletCloth::RORC_VERSION |
#pt | 粒子特效列表 | ptxFxList::RORC_VERSION |
#mt / #map / #typ | 元数据(PSO)/ 地图数据 / 地图类型 | — |
#nv / #sc / #nd | 导航网格 / 脚本 / 路网节点 | — |
证据:src/dev_ng/rage/framework/src/fwsys/fileExts.h:30-43、src/dev_ng/rage/framework/src/fwsys/fileExts.h:294-320。
2. datResource:运行时指针修复#
2.1 datResourceMap#
加载时的块表:VirtualCount/PhysicalCount/RootVirtualChunk、VirtualBase(根对象所在块的目标地址)、Chunks[128](每项 SrcAddr/DestAddr/Size,虚拟块在前)以及“上次命中”缓存 src/dev_ng/rage/base/src/data/resource.h:42-108。 Xenon/PS3 的 RESOURCE_HEADER 模式下还区分 RPF/字典/脚本头类型,可把小 RPF 头放入专用 MEMTYPE_HEADER_VIRTUAL 堆 src/dev_ng/rage/base/src/data/resource.h:38-40、src/dev_ng/rage/base/src/data/resourceheader.h:13-29。
2.2 修复栈与算法#
datResource(map, debugName, isDefrag)构造时压入 TLS 栈datResource_sm_Current(注释称为规避 Sony TLS 问题而用全局 TLS 变量而非类静态),析构时弹出;构造期间++g_DisableInitNan避免资源对象被 NaN 填充src/dev_ng/rage/base/src/data/resource.h:21-28、src/dev_ng/rage/base/src/paging/base.cpp:2132-2191。- 默认
BINARY_SEARCH_FIXUPS:构造时把块的源区间与目标区间分别排序;GetFixup(ptr)在源区间二分查找所属块,返回DestAddr - SrcAddr;找不到则Quitf(ERR_SYS_INVALIDRESOURCE_5, "Invalid fixup ...")。ContainsThisAddress在目标区间二分。Xenon/PS3 在已知引用开启时另有表查找加速(TABLE_SEARCH_FIXUPS,仅更新线程)src/dev_ng/rage/base/src/data/resource.h:175-187、src/dev_ng/rage/base/src/paging/base.cpp:2196-2260。 - 静态模板
datResource::Fixup(ptr):若当前有修复且指针字段本身位于当前资源内,则修复;否则(非资源上下文)把指针置 NULL。datResource::Place(ptr, ...)修复后再调用T::Place(newPtr, rsc, ...)递归构造被拥有对象src/dev_ng/rage/base/src/data/resource.h:267-318。 - SPU 版只支持单一连续容器、常量偏移
src/dev_ng/rage/base/src/data/resource.h:110-171。
2.3 资源构造约定#
每个可资源化类型提供“资源构造函数” T(datResource&) 与静态 Place:DECLARE_PLACE(T) 声明、IMPLEMENT_PLACE(T) 实现为 ::new (that) T(rsc);不需要修复的简单类用 DECLARE_DUMMY_PLACE;需要按枚举创建不同子类的虚工厂类自写 Place。__TOOL 构建下 IMPLEMENT_PLACE 为空实现 src/dev_ng/rage/base/src/data/resource.h:333-420。
资源内的指针字段使用包装类型,使资源构造函数在成员初始化阶段自动完成修复:
| 类型 | 语义 | 资源构造行为 | 证据 |
|---|---|---|---|
datRef<T> | 非拥有引用 | datResource::Fixup(ptr) | src/dev_ng/rage/base/src/data/struct.h:110-121 |
datOwner<T> | 拥有指针 | datResource::Place(ptr[, p1, p2]),递归放置子对象 | src/dev_ng/rage/base/src/data/struct.h:124-133 |
pgRef<T> | 非拥有引用(可参与已知引用跟踪) | 有当前资源则 PointerFixup,否则置 0 | src/dev_ng/rage/base/src/paging/ref.h:17-60 |
pgOwner<T> | 拥有指针(可碎片整理版本) | datResource::Place(ptr) | src/dev_ng/rage/base/src/paging/owner.h:15-60 |
pgHandle<T> | 经句柄表的双重间接 | 仅在 ENABLE_DEFRAGMENTATION 时有意义 | src/dev_ng/rage/base/src/paging/handle.h:46-70 |
atArray 等容器也有资源构造函数(见 math-and-containers.md)。DECLARE_PADDED_POINTER 当前恒为普通指针(32 位填充分支被 && 0 关闭) src/dev_ng/rage/base/src/data/struct.h:15-23。
2.4 DeclareStruct 与字节交换(构建期)#
__DECLARESTRUCT = !__FINAL && !__SPU && (!__PPU || __DEV) && !RSG_ORBIS。资源编译器为每个类型实现 DeclareStruct(datTypeStruct&),用 STRUCT_FIELD 列出字段;datSwapper 系列在 g_ByteSwap(仅资源编译器中为变量)为真时交换字节序,指针字段先 datRelocatePointer(把构建堆地址改写为 0x5…/0x6… 源地址)再交换 src/dev_ng/rage/base/src/data/struct.h:43-106。这就是“跨平台构建”的核心:PC 上的资源编译器以目标平台的字节序、指针宽度与叶尺寸生成镜像(平台判定见 system-and-diagnostics.md 的 g_sysPlatform)。
3. pgBase:可分页对象基类#
3.1 结构#
pgBase 只有一个虚析构(Destroy())与一个数据成员 pgBaseNode* m_FirstNode。对顶层资源对象,m_FirstNode 指向资源内嵌的 pgBasePageMap(VirtualCount/PhysicalCount/RootVirtualChunk/Subtype + 每块一个 size_t PageInfo[]),其后可链接若干 pgBaseMetaData(按 Type 在 pgBaseMetaDataType 注册表中查找放置函数) src/dev_ng/rage/base/src/paging/base.h:155-369、src/dev_ng/rage/base/src/paging/base_spu.h:18-74。构建期 ReadyBuild 首先在虚拟堆中分配最坏情况大小的页表、挂到顶层对象 m_FirstNode,再反复打包块(虚拟最多占一半的 128 个桶,物理用剩余)直到页表尺寸稳定 src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1216-1275。
3.2 生命周期#
图证据:PlaceStream / Load 模板 src/dev_ng/rage/base/src/paging/rscbuilder.h:416-430、src/dev_ng/rage/base/src/paging/rscbuilder.h:533-543;pgBase::pgBase src/dev_ng/rage/base/src/paging/base.cpp:246-265;Destroy/FreeMemory src/dev_ng/rage/base/src/paging/base.cpp:322-412;MakeDefragmentable/RegenerateMap src/dev_ng/rage/base/src/paging/base.cpp:442-520。
要点:
- 页表压缩:
PageInfo[i] = DestAddr | log2(Size/leafSize),要求块地址低位为 0、log2 < 16(用TrapGE/TrapNZ而不是 Assert,Release 中也生效)src/dev_ng/rage/base/src/paging/base.cpp:468-480。 - 释放:若资源虚拟分配器
SupportsAllocateMap(),整张表一次FreeMap;否则逐块释放虚拟块(非 Final 下先以0xEE填充)与物理块;USE_SPARSE_MEMORY时根块最后用DeferredFree释放src/dev_ng/rage/base/src/paging/base.cpp:342-412。 - Orbis:放置时把物理块内存类型改为 CPU/GPU 只读、Garlic 总线写回
src/dev_ng/rage/base/src/paging/base.cpp:254-262。 Validate(userData)(非 Final)检查每块仍为有效分配且 user data 匹配,用于定位过早释放src/dev_ng/rage/base/src/paging/base.cpp:414-440。sm_ThreadedPlace(DECLARE_THREADED_PLACE())声明该类型允许在流式线程上放置src/dev_ng/rage/base/src/paging/base.h:41-43、src/dev_ng/rage/base/src/paging/base.h:161-162。
3.3 已知引用与碎片整理:在本代码中关闭#
paging/knownrefpool_config.h 中 ENABLE_KNOWN_REFERENCES 0、ENABLE_DEFRAGMENTATION (ENABLE_KNOWN_REFERENCES && 1) → 两者均为 0 src/dev_ng/rage/base/src/paging/knownrefpool_config.h:1-2。因此 ADD_KNOWN_REF/REMOVE_KNOWN_REF/UPDATE_KNOWN_REF 退化为空或普通赋值 src/dev_ng/rage/base/src/paging/base.h:126-144,pgHandle 双重间接与 PatchAllReferences 碎片整理路径不生效,pgBase 构造走 !ENABLE_KNOWN_REFERENCES 分支(仅修复 m_FirstNode 并填页表) src/dev_ng/rage/base/src/paging/base.cpp:248-265。源码中 datResource_IsDefragmentation 在此配置下为常量 false src/dev_ng/rage/base/src/data/resource.h:24-28。
3.4 派生基类#
| 类 | 用途 | 证据 |
|---|---|---|
pgBaseRefCounted | 原子引用计数,Release() 归零即 delete this | src/dev_ng/rage/base/src/paging/base.h:373-411 |
pgBaseAllocated | 非资源对象自带 3 个 u32 页表存储 | src/dev_ng/rage/base/src/paging/base.h:413-420 |
pgBasePlatformNeutral | 32/64 位布局一致(32 位时补 8 字节),放置前 BeforePlace(that, originatingPlatform) 处理来源平台差异;psoResourceData 继承自它 | src/dev_ng/rage/base/src/paging/base.h:422-445 |
pgArray<T> | 资源化数组基类 | src/dev_ng/rage/base/src/paging/array.h:20 |
4. pgRscBuilder:构建与加载入口#
4.1 构建(__RESOURCECOMPILER)#
BeginBuild(secondPass, heapSize, ...):按目标平台配置叶尺寸,创建虚拟/物理两个singlePassAllocator并组合成rscAllocator,替换当前线程的Current/Container分配器——此后所有new都落入资源堆(ONE_PASS_BUILD = 1)src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1166-1214、src/dev_ng/rage/base/src/paging/rscbuilder.h:23、src/dev_ng/rage/base/src/paging/rscbuilder.h:104-145。- 构造对象图(顶层对象必须是
BeginBuild后第一个分配)。 ReadyBuild(top):分配页表、打包桶、给所有分配分配最终(源)地址、重定位顶层指针。- 调用
DeclareStruct(字节交换/指针重定位)。 SaveBuild(name, "#xx", version, ...):复制堆、压缩、写头。EndBuild(top):恢复分配器,删除构建堆src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1277-1295。
4.2 加载#
| 入口 | 用途 | 证据 |
|---|---|---|
Load<T>(t, name, ext, version[, u, v, w]) | 同步:LoadBuild(内部 BeginStream+阻塞 EndStream)→ datResource → T::Place → PostPlace | src/dev_ng/rage/base/src/paging/rscbuilder.h:416-489、src/dev_ng/rage/base/src/paging/rscbuilder.cpp:413-448 |
BeginStream/OpenStream/ContinueStream/EndStream/CancelStream | 异步三段式 | src/dev_ng/rage/base/src/paging/rscbuilder.h:239-380 |
PlaceStream<T>(t, info, map, debugName[, ...]) | 数据已由流式系统读入 map 后只做放置;被 framework/游戏大量使用(导航网格、路网、录像、音频几何等) | src/dev_ng/rage/base/src/paging/rscbuilder.h:533-570;调用例:src/dev_ng/rage/framework/src/ai/navmesh/navmesh.cpp:395、src/dev_ng/game/vehicleAi/pathfind.cpp:344 |
AllocateMap(map) | 若分配器支持则整表分配;否则逐块分配虚拟/物理,失败回滚并返回 false | src/dev_ng/rage/base/src/paging/rscbuilder.cpp:315-395 |
ValidateMap(name, map, hdr) | GenerateMap + AllocateMap,带内存跟踪标签 | src/dev_ng/rage/base/src/paging/rscbuilder.cpp:397-410 |
5. pgStreamer:异步读取与解压#
pgStreamer 是 RPF 之上的底层异步读服务;framework 的 strStreamingLoader 通过 pgStreamer::Read 发出请求(src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2102)。
- 句柄:
Open(filename, &size, resourceVersion)解析路径到 RPF 设备(PC 或ENABLE_RAW_STREAMER时非 RPF 文件走pgRawStreamer),句柄为fiCollection::MakeHandle(packfileIndex, entryIndex);若给定resourceVersion,则比较包条目中的版本与resourceVersion & 255,不符则报错/断言并返回Errorsrc/dev_ng/rage/base/src/paging/streamer.cpp:2074-2117。 - 设备与线程:
Device { OPTICAL, HARDDRIVE }(DUAL_QUEUE_SUPPORT=1);每个设备一个pgStreamerDeviceHandler,含一个zlibInflater、一个读线程pgReadData("[RAGE] DVD/HDD Reader")、一个处理线程("[RAGE] DVD/HDD Streamer"),以及CRITICAL_QUEUE/NORMAL_QUEUE两条请求队列src/dev_ng/rage/base/src/paging/streamer.h:76-80、src/dev_ng/rage/base/src/paging/streamer.cpp:659-697。 - 容量:同时在途请求上限
MaxRequests = 32(位图池大小);读缓冲每块MAX_READ = 512 KB,光驱 3 块、硬盘 24 块src/dev_ng/rage/base/src/paging/streamer.cpp:661-668、src/dev_ng/rage/base/src/paging/streamer_internal.h:338-342、src/dev_ng/rage/base/src/paging/streamer_internal.h:391-393。 - 读请求:
Read(handle, destList[], destCount, offset, sema|callback, ...),偏移必须 2 KB 对齐;标志LOCK/UNLOCK/ENCRYPTED/UNCACHED/CRITICAL,CRITICAL或设备被锁时进入关键队列src/dev_ng/rage/base/src/paging/streamer.h:128-187、src/dev_ng/rage/base/src/paging/streamer.cpp:1204-1205。取消后完成回调/信号量仍会触发src/dev_ng/rage/base/src/paging/streamer.h:118-126。 - 处理:读线程把压缩数据读入读缓冲,处理线程用
zlibInflater解压到目标块(加密资源先解密),也支持直接拷贝到显存(CopyVram)src/dev_ng/rage/base/src/paging/streamer.cpp:1372-1404、src/dev_ng/rage/base/src/paging/streamer.cpp:1491-1503、src/dev_ng/rage/base/src/paging/streamer.cpp:1600。 - 初始化:
InitClass(cpu, prio=PRIO_HIGHEST, cpu2)只执行一次,初始化请求池、两个设备处理器、AES 对象;调试参数-logstreaming(CSV 日志 + 刷新线程)、-slowstreaming、-emulateoptical、-singlequeuesrc/dev_ng/rage/base/src/paging/streamer.cpp:2161-2211、src/dev_ng/rage/base/src/paging/streamer.cpp:199-201。 - 观测回调:Open/Read/Idle/ReadBulk/BeginProcess/Process/Decompress/ErrorDump 等回调供上层统计与调试
src/dev_ng/rage/base/src/paging/streamer.h:38-48、src/dev_ng/rage/base/src/paging/streamer.h:257-313。
streamer.cpp 中还存在与防篡改相关的成员(例如初始化时清零的 sm_tamperCrash),按安全边界本页不展开,见 23-debug-security/security-overview.md。
6. pgDictionary<T>:按哈希命名的资源集合#
纹理字典(.ytd)、可绘制体字典、剪辑字典等都是 pgDictionary<T>:
- 数据:
atArray<u32> m_Codes(有序哈希)与atArray<datOwner<T>> m_Entries(并行数组),父字典指针m_Parent、引用计数src/dev_ng/rage/base/src/paging/dictionary.h:444-451。 - 键:
ComputeHash(name) = atStringHash(name)(大小写不敏感,见 math-and-containers.md)src/dev_ng/rage/base/src/paging/dictionary.h:298-301。 - 查找:
LookupLocal在本字典二分查找;Lookup沿m_Parent链逐级查找(例如“纹理字典父链”,模型纹理找不到时去父 TXD)src/dev_ng/rage/base/src/paging/dictionary.h:261-296。 - 插入:
AddEntry(code, data)线性找位置后插入保持有序,重复哈希返回 falsesrc/dev_ng/rage/base/src/paging/dictionary.h:333-371。 - “当前字典”栈:
sm_CurrentDict为 TLS(DICTIONARY_THREAD = __THREAD,因ENABLE_MTPLACEMENT_CODE 1),Push()/Pop()构成链;资源构造某模型时,外层先把其纹理字典压栈,使模型内纹理引用在放置时能解析(源码称之为 “EVIL C++ VOODOO” 的辅助类)src/dev_ng/rage/base/src/paging/dictionary.h:19-25、src/dev_ng/rage/base/src/paging/dictionary.h:134-207、src/dev_ng/rage/base/src/paging/dictionary.h:454-460。 - 析构时
Release每个条目,并断言没有悬挂引用src/dev_ng/rage/base/src/paging/dictionary.h:69-81。
约束与边界行为#
- 资源版本在 RPF 中只保留 8 位:代码版本变化若
& 255相同则无法检出;SaveBuild拒绝version & 255 == 0。 GetFixup找不到所属块时直接Quitf(ERR_SYS_INVALIDRESOURCE_5)——损坏或版本不符的资源表现为崩溃而非加载失败。datResource::Fixup在没有当前修复上下文时会把指针置 NULL:普通(非资源)构造路径中误用资源构造函数会清空指针。- 单个资源最多 128 块;页表
log2(size/leaf)必须 < 16。 - 修复栈与当前字典均为 TLS,允许多线程并行放置,但同一线程上的
datResource必须严格后进先出。 - 碎片整理/已知引用在本配置下关闭,因此资源内存在生命周期内不移动;任何依赖
pgHandle双重间接的代码实际退化为直接指针。
未决问题 / 证据不足#
pgStreamer处理线程的完整状态机、解密与CopyVram的触发条件、pgRawStreamer细节只做了入口级确认。singlePassAllocator打包算法(桶选择、maxAlloc等,见src/dev_ng/rage/base/src/paging/rscbuilder.cpp:1028-1061)未逐行分析。- 未确认游戏发行配置下
ENABLE_BUDDY_ALLOCATOR与SupportsAllocateMap()的实际取值(属于 01-runtime/memory-and-pools.md 范围)。