[Bug] tbl.ts 字符串表解码器偏差:线性假想索引未对齐暗黑2官方哈希桶(Hash Table)键值格式 #8

Closed
opened 2026-09-14 07:01:29 +00:00 by troytt · 1 comment
Owner

Parent Issue: #7 ([Code Review] 整体代码质量、架构缺陷与技术债务全景审查报告)

模块位置

src/formats/tbl.ts, scripts/verify-tbl.ts, scripts/lib/tbl-writer.ts

问题背景与现象

  • tbl.ts 目前使用的是简化的 [CRC][Count][Offsets...][UTF-16LE Strings] 线性顺序布局。
  • 作者在文件头注释中误认为“带哈希表的实现是后来扩展格式”,因此只做了自闭环的合成验证(通过 lib/tbl-writer.ts 写入并读回)。
  • 事实:Diablo II 原版的所有三张官方字符串表(string.tbl, expansionstring.tbl, patchstring.tbl)均是基于 TBLHeader(含 crc, num_elements, hash_size, version, index_offset, max_tries, file_size)与哈希桶(Hash Table)索引的键值对存储结构。

造成的影响

  1. 官方中文语言包 data\local\LNG\CHI\string.tbl 解码出全部为乱码(详见 HANDOVER.md:119)。
  2. 中文场景名被迫退化为静态手工硬编码映射表(src/game/level-names-zh.ts)。
  3. 阻碍了 Issue #6 中所有城镇 NPC 名字、对话台词、任务描述、装备词缀与技能名的原版动态读取。

解决建议

  1. 依照原版格式规范定义 TBLHeader、HashNode、KeyNode 结构体;
  2. 实现基于 32 位哈希算法与线性探测(max_tries)的键检索逻辑;
  3. 支持以 null 结尾的单字节/多字节字符集解码(ANSI / Code-Page 936 / UTF-8);
  4. 替换 scripts/lib/tbl-writer.ts,并让 scripts/verify-tbl.ts 直接对齐原版 string.tbl 真文件验证。
> **Parent Issue**: #7 ([Code Review] 整体代码质量、架构缺陷与技术债务全景审查报告) ## 模块位置 `src/formats/tbl.ts`, `scripts/verify-tbl.ts`, `scripts/lib/tbl-writer.ts` ## 问题背景与现象 - `tbl.ts` 目前使用的是简化的 `[CRC][Count][Offsets...][UTF-16LE Strings]` 线性顺序布局。 - 作者在文件头注释中误认为“带哈希表的实现是后来扩展格式”,因此只做了自闭环的合成验证(通过 `lib/tbl-writer.ts` 写入并读回)。 - **事实**:Diablo II 原版的所有三张官方字符串表(`string.tbl`, `expansionstring.tbl`, `patchstring.tbl`)均是基于 `TBLHeader`(含 `crc`, `num_elements`, `hash_size`, `version`, `index_offset`, `max_tries`, `file_size`)与哈希桶(Hash Table)索引的键值对存储结构。 ## 造成的影响 1. 官方中文语言包 `data\local\LNG\CHI\string.tbl` 解码出全部为乱码(详见 `HANDOVER.md:119`)。 2. 中文场景名被迫退化为静态手工硬编码映射表(`src/game/level-names-zh.ts`)。 3. 阻碍了 Issue #6 中所有城镇 NPC 名字、对话台词、任务描述、装备词缀与技能名的原版动态读取。 ## 解决建议 1. 依照原版格式规范定义 `TBLHeader`、`HashNode`、`KeyNode` 结构体; 2. 实现基于 32 位哈希算法与线性探测(`max_tries`)的键检索逻辑; 3. 支持以 null 结尾的单字节/多字节字符集解码(ANSI / Code-Page 936 / UTF-8); 4. 替换 `scripts/lib/tbl-writer.ts`,并让 `scripts/verify-tbl.ts` 直接对齐原版 `string.tbl` 真文件验证。
Author
Owner

已修复 — fix(formats): 按官方哈希桶格式重写 tbl 解码器与编码器 (f4dd55b)

原实现按顺序表解析,与真实 .tbl 的哈希桶布局不符。现已按实际格式重写:

  • 头部 21 字节 LE:u16 crc, u16 numElements, u32 hashTableSize, u8 version, u32 stringOffset, u32 maxTries, u32 fileSize;其后是 numElements 个 u16 索引;再其后是 hashTableSize 个 17 字节节点。
  • key 按原始大小写参与哈希,不可转大写 —— 这是本次最关键的修正。用 patchstring 实测:原始大小写 1169/1169 命中,转大写只有 3/1169。
  • 读取无需重算哈希:遍历节点取 isActive != 0 即可;valueLength 含结尾 NUL。

变异测试(由我方独立选定,非实现方自选): 把哈希 key 强制转大写 → 出现 767 处不匹配,测试转红;还原后转绿。

### 已修复 — `fix(formats): 按官方哈希桶格式重写 tbl 解码器与编码器` (f4dd55b) 原实现按顺序表解析,与真实 `.tbl` 的哈希桶布局不符。现已按实际格式重写: - 头部 21 字节 LE:`u16 crc, u16 numElements, u32 hashTableSize, u8 version, u32 stringOffset, u32 maxTries, u32 fileSize`;其后是 `numElements` 个 u16 索引;再其后是 `hashTableSize` 个 17 字节节点。 - **key 按原始大小写参与哈希,不可转大写** —— 这是本次最关键的修正。用 `patchstring` 实测:原始大小写 **1169/1169** 命中,转大写只有 **3/1169**。 - 读取无需重算哈希:遍历节点取 `isActive != 0` 即可;`valueLength` 含结尾 NUL。 **变异测试(由我方独立选定,非实现方自选):** 把哈希 key 强制转大写 → 出现 **767 处不匹配**,测试转红;还原后转绿。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Reference: troytt/diablo2-web#8
No description provided.