分析对象:Torchlight2.exe(v1.25.9.5,基址 0x400000,加载函数 sub_604D20)
核心定位:解析规则模板如何将 [CHUNK_RANDOM].TYPE 字符串映射至内存 CHUNKTYPE、物理目录及具体变体 .LAYOUT。

0. 结论速览 ​

[CHUNK_RANDOM] 中的 TYPE 并非物理磁盘目录名,底层亦不存在前缀模糊匹配或去除 _LANDMARK 等特殊剥离规则。它精确指向同一份模板内部声明的 [CHUNKTYPE].NAME。

引擎底层的完整装配链路如下:

text
CHUNK_RANDOM.TYPE
        │ 精确字符串比较
        ▼
CHUNKTYPE.NAME  (CChunkType + 0x44)
        │
        ├─ FOLDER 缺省为 NAME
        │  最终目录 = 模板所在目录 + FOLDER + "/"
        │
        ├─ 存在 INCLUSIVE_FILES 清单
        │      仅加载声明的 FILE + ".LAYOUT"
        │
        └─ 未声明 INCLUSIVE_FILES
               枚举最终目录,排除 /MERGE/,按名排序
                       │
                       ▼
                    CChunk 变体列表

此前部分自动化检查工具采用「目录名精确比对 → 去除 _LANDMARK 后缀 → TYPE_ 前缀推导」的启发式算法,导致 170 处合法引用被误报为解析失败;逆向确认该逻辑并不符合引擎原生语法,引擎始终采用同模板内声明的精确全名匹配。

1. 静态汇编证据 ​

目标为 Torchlight2.exe v1.25.9.5,IDA imagebase 0x00400000,加载函数为 sub_604D20。

地址行为
0x606DD5..0x606E8C收集全部 CHUNKTYPE,读取 NAME
0x606FEA..0x6070C2getString(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 字节):

text
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 DD

83 C0 44 把当前 CChunkType* 调整到 NAME 字段,随后调用宽字符串 operator==;失败就递增索引并继续线性扫描。因此,模板出现同名声明时,首个声明 胜出。

字段与对象布局 ​

本轮确认的必要字段如下:

对象偏移语义
CChunkType+0x18拼好的最终文件夹路径
CChunkType+0x34INCLUSIVE_FILES 成功加载的布局指针向量
CChunkType+0x44NAME 宽字符串
CChunk+0x08所属类型/类型索引
CChunk+0x14.LAYOUT 路径宽字符串

FOLDER 不存在时,CDataGroup::getString 的默认参数正是刚读出的 NAME。这个 细节解释了绝大多数普通模板为什么看起来像“TYPE 就是目录”:它们只是通过默认值 恰好同名;一旦模板显式写 FOLDER,目录启发式便会错。

全库重跑结果 ​

命令:

powershell
python -B tools/m4_seam_check.py --json build/m4/seam_check.json

固定 seed 1234 的结果:

指标数值
模板186
有预设、实际进入检查的模板119
预设286
CHUNK_RANDOM 引用2,479
精确匹配 CHUNKTYPE.NAME2,477(99.9%)
通过目录枚举得到可加载变体2,009
通过 INCLUSIVE_FILES 得到可加载变体316
成功摆下2,325
匹配到类型但无可加载布局152
模板未声明该类型2
实际覆盖的不同瓦片610
接触相邻对3,015
真接缝洞0

旧 170 个启发式遗漏中,有 16 个通过正确的 FOLDER/INCLUSIVE_FILES 依赖得到准确解析。其余未命中项也得以重新定性:其成因属于发行版原始资产缺失或模板可达性遗留,而非规则语法未明。

3. 剩余 154 处未决引用的成因分析 ​

其中 152 个引用能够在同模板中定位到匹配的 CHUNKTYPE.NAME,但按原版加载规则无法映射到任何物理磁盘布局文件:

规则模板未决引用数
ACT1/SNOW/RULES.TEMPLATE144
TEST/RULES.TEMPLATE4
ACT2_CAVES/MANAVENT_RULES.TEMPLATE1
DRAGON/CASTLERUINS_RULES.TEMPLATE1
MAINMENUS/MAINMENU_SPLASHRULES.TEMPLATE1
TEST/TESTROOM_RULES.TEMPLATE1

另有两项属于模板内缺失对应类型声明:

  • ACT3_Z2/NG_DWARFARMORY_RULES.TEMPLATE 中的 1X1SINGLE_ROOM_BOSS
  • TESTROOM/TESTROOMRULES.TEMPLATE 中的 1X1SINGLE_ROOM_BOSS

以 ACT1/SNOW/RULES.TEMPLATE 为例:其声明的 25 个类型按原版规则均寻址至 ACT1/SNOW/<NAME>/ 路径,但在最终发行版资源中该路径仅存规则模板及其 BINDAT,同名瓦片实际散落于上级目录 ACT1/<NAME>/。原版 C++ 解析逻辑中并不存在自动回溯父级目录的回落分支;逆向工具不应为了强行消除缺失告警而引入未经证实的回退规则,应严格如实反映引擎解析行为。

4. 对重制与解析工具的工程约束 ​

在实现规则解析器时,应严格遵循原版装配阶段:先建立全量 CHUNKTYPE 映射表,再解析并派发 [LAYOUT] 预设。针对每个 CHUNK_RANDOM.TYPE 执行与宽字符串等价的精确匹配;变体布局检索必须严格限制在该类型自身的 FOLDER 或 INCLUSIVE_FILES 范围内。严禁跨兄弟目录扫描、前缀模糊匹配或擅自剥离 _LANDMARK 等命名后缀。