RULES.TEMPLATE 的 CHUNK TYPE 解析
结论
[CHUNK_RANDOM] 的 TYPE 不是磁盘目录名,也不存在前缀匹配或去 _LANDMARK 的特殊规则。它精确引用同一份模板里的 [CHUNKTYPE].NAME。
原版的完整链路是:
CHUNK_RANDOM.TYPE
│ 精确字符串比较
▼
CHUNKTYPE.NAME (CChunkType + 0x44)
│
├─ FOLDER 缺省 NAME
│ 最终目录 = 模板自身目录 + FOLDER + "/"
│
├─ 有 INCLUSIVE_FILES
│ 只加载 FILE + ".LAYOUT"
│
└─ 无 INCLUSIVE_FILES
枚举最终目录,排除 /MERGE/,排序
│
▼
CChunk 列表这关闭了 HANDOFF_RE_BACKLOG.md 的 C-1。原检查器使用“目录精确 → 去 _LANDMARK → TYPE_ 前缀”的启发式,所以把 170 个引用笼统报成“TYPE 解析不出”;那不是引擎语法。
静态证据
目标为 Torchlight2.exe v1.25.9.5,IDA imagebase 0x00400000,加载函数为 sub_604D20。
| 地址 | 行为 |
|---|---|
0x606DD5..0x606E8C | 收集全部 CHUNKTYPE,读取 NAME |
0x606FEA..0x6070C2 | getString(FOLDER, NAME);模板目录与结果拼接,保证尾部斜杠 |
0x6073D9..0x6074D2 | 有 INCLUSIVE_FILES 时,逐项读取 FILE、补 .LAYOUT、存在才加入 |
0x60776B..0x6078B6 | 无清单时枚举目录;0x607819 排除 /MERGE/;随后排序 |
0x6079F1..0x607A7B | 创建 CChunk,保存所属类型和 .LAYOUT 路径 |
0x60836C..0x6083E3 | 读取 CHUNK_RANDOM.TYPE,从类型向量头线性精确比较 CChunkType+0x44 |
类型比较核心字节(0x6083B5,48 字节):
8B 46 30 3B 6E 38 73 03 8D 04 A8 8B 00 8D 8C 24
60 01 00 00 83 C0 44 51 50 FF 15 68 68 11 02 83 C4
08 84 C0 75 08 45 3B 6E 34 72 D4 EB 02 8B DD83 C0 44 把当前 CChunkType* 调整到 NAME 字段,随后调用宽字符串 operator==;失败就递增索引并继续线性扫描。因此,模板出现同名声明时,首个声明 胜出。
字段与对象布局
本轮确认的必要字段如下:
| 对象 | 偏移 | 语义 |
|---|---|---|
CChunkType | +0x18 | 拼好的最终文件夹路径 |
CChunkType | +0x34 | INCLUSIVE_FILES 成功加载的布局指针向量 |
CChunkType | +0x44 | NAME 宽字符串 |
CChunk | +0x08 | 所属类型/类型索引 |
CChunk | +0x14 | .LAYOUT 路径宽字符串 |
FOLDER 不存在时,CDataGroup::getString 的默认参数正是刚读出的 NAME。这个 细节解释了绝大多数普通模板为什么看起来像“TYPE 就是目录”:它们只是通过默认值 恰好同名;一旦模板显式写 FOLDER,目录启发式便会错。
全库重跑结果
命令:
python -B tools/m4_seam_check.py --json build/m4/seam_check.json固定 seed 1234 的结果:
| 指标 | 数值 |
|---|---|
| 模板 | 186 |
| 有预设、实际进入检查的模板 | 119 |
| 预设 | 286 |
CHUNK_RANDOM 引用 | 2,479 |
精确匹配 CHUNKTYPE.NAME | 2,477(99.9%) |
| 通过目录枚举得到可加载变体 | 2,009 |
通过 INCLUSIVE_FILES 得到可加载变体 | 316 |
| 成功摆下 | 2,325 |
| 匹配到类型但无可加载布局 | 152 |
| 模板未声明该类型 | 2 |
| 实际覆盖的不同瓦片 | 610 |
| 接触相邻对 | 3,015 |
| 真接缝洞 | 0 |
旧 170 个启发式 miss 中,16 个由正确的 FOLDER/INCLUSIVE_FILES 关系救回。 剩余问题也被重新定性:它们不是“TYPE 语法未知”,而是发行语料或模板可达性债。
剩余 154 个为什么不能擅自“修复”
其中 152 个引用能找到 CHUNKTYPE.NAME,但按原版规则没有任何可加载布局:
| 模板 | 引用数 |
|---|---|
ACT1/SNOW/RULES.TEMPLATE | 144 |
TEST/RULES.TEMPLATE | 4 |
ACT2_CAVES/MANAVENT_RULES.TEMPLATE | 1 |
DRAGON/CASTLERUINS_RULES.TEMPLATE | 1 |
MAINMENUS/MAINMENU_SPLASHRULES.TEMPLATE | 1 |
TEST/TESTROOM_RULES.TEMPLATE | 1 |
另外两项连类型声明都没有:
ACT3_Z2/NG_DWARFARMORY_RULES.TEMPLATE的1X1SINGLE_ROOM_BOSSTESTROOM/TESTROOMRULES.TEMPLATE的1X1SINGLE_ROOM_BOSS
最显眼的是 ACT1/SNOW/RULES.TEMPLATE:它的 25 个类型按原版规则都指向 ACT1/SNOW/<NAME>/,但发行目录里只有模板及其 BINDAT;相似瓦片实际位于上一级 ACT1/<NAME>/。静态代码没有“退回父目录”的分支,因此检查器也不能为了把 skipped 降到零而添加这种猜测。需要另行证明这些旧模板是否可达,或确认发行资源是否缺失。
对复刻实现的要求
实现时应先解析整份模板的 CHUNKTYPE 表,再解析预设。对每个 CHUNK_RANDOM.TYPE 做大小写语义与原版宽字符串一致的精确查找;候选布局必须来自该 类型自己的 FOLDER/INCLUSIVE_FILES。不要扫描兄弟目录,不要做前缀匹配,也不要把 _LANDMARK 当语法后缀剥掉。
可执行实现与真值登记位于:
tools/m4_seam_check.py原版游戏分析/reversed_cpp/REGISTRY/chunk_type_resolution.jsontests/truth/test_m4_seam_check.pybuild/m4/seam_check.json