[Parity][frontend/netproto][S2] MCP 0x19 角色 statstring 进度值(progression)缺少右移 1 位,导致难度解锁判定翻倍错位 #738

Closed
opened 2026-10-03 10:50:03 +00:00 by troytt · 0 comments
Owner

问题描述

在解析 MCP 0x19(MCP_CHARLIST2)返回的 33 字节角色外观与状态字节串(statBytes)时,src/netproto/mcp/packets.ts 与 src/client/frontend/flow-state.ts 直接使用 statBytes[27] & 0x1f 提取角色进度 progression,未按 1.13c FOG_Encode14BitsToString / D2PackedClientSaveFlags 规范右移 1 位(应为 (statBytes[27] >>> 1) & 0x1f)。

1.13c Ground Truth 根因分析

  1. D2PackedClientSaveFlags 位域定义(D2Game.dll CLIENTS_FillCharacterPreviewInfo @ 0x6FC32810 / Clients.h):
    • bits 0..6:状态位(0x01 init, 0x04 hardcore, 0x08 dead, 0x20 expansion, 0x40 ladder)
    • bit 7:bUnkFlag0x80
    • bits 8..12(共 5 bits):nProgression(CLIENTSAVEFLAG_CHARACTER_PROGRESSION_BIT = 8,资料片 0..15,原版 0..12)
    • bit 13:bWeaponSwitch(0x2000)
  2. 14-bit 双字节编码(Fog.#10085 FOG_Encode14BitsToString / Fog.#10086 FOG_Decode14BitsFromString):
    • statBytes[26] = (nPackedValue & 0x7f) | 0x80(对应 nPackedValue 的 bits 0..6)
    • statBytes[27] = ((nPackedValue >>> 7) & 0x7f) | 0x80(对应 nPackedValue 的 bits 7..13)
    • 因此在 statBytes[27] 中:
      • bit 0 是 nPackedValue 的 bit 7(bUnkFlag0x80)
      • bits 1..5 才是 5 位 nProgression,即 (statBytes[27] >>> 1) & 0x1f
      • bit 6 是 bWeaponSwitch
      • bit 7 是非零掩码 0x80

影响表现

直接用 statBytes[27] & 0x1f 会把 (progression << 1) & 0x1f 当作进度值(翻倍后对 32 取模):

  • Normal Act 3(progression = 3, statBytes[27] = 0x86)被误判为 progression = 6,前端错误解锁噩梦难度按钮;
  • 刚通关普通难度(progression = 5, statBytes[27] = 0x8a)被误判为 progression = 10,前端错误解锁地狱难度按钮(点击创建地狱房间会被 d2cs 拒绝);
  • 资料片全通 Matriarch/Patriarch(progression = 15, statBytes[27] = 0x9e)被解析为 30。

修复范围

  • src/netproto/mcp/packets.ts:parseCharStatstring 中将 progression 修正为 (actAndDiff >>> 1) & 0x1f,并基于 progression 计算 act。
  • src/client/frontend/flow-state.ts:getAllowedDifficultiesForSelectedCharacter 回退分支修正为 (statBytes[27]! >>> 1) & 0x1f,同步更新注释。
  • src/client/frontend/statstring-avatar.ts:同步修正字节偏移 27 的位域注释。
  • 补充并更新单元测试验证 FOG_Encode14BitsToString 编码与难度解锁边界。
## 问题描述 在解析 MCP `0x19`(`MCP_CHARLIST2`)返回的 33 字节角色外观与状态字节串(`statBytes`)时,`src/netproto/mcp/packets.ts` 与 `src/client/frontend/flow-state.ts` 直接使用 `statBytes[27] & 0x1f` 提取角色进度 `progression`,未按 1.13c `FOG_Encode14BitsToString` / `D2PackedClientSaveFlags` 规范右移 1 位(应为 `(statBytes[27] >>> 1) & 0x1f`)。 ## 1.13c Ground Truth 根因分析 1. **`D2PackedClientSaveFlags` 位域定义(`D2Game.dll` `CLIENTS_FillCharacterPreviewInfo` @ `0x6FC32810` / `Clients.h`)**: - `bits 0..6`:状态位(`0x01` init, `0x04` hardcore, `0x08` dead, `0x20` expansion, `0x40` ladder) - `bit 7`:`bUnkFlag0x80` - `bits 8..12`(共 5 bits):`nProgression`(`CLIENTSAVEFLAG_CHARACTER_PROGRESSION_BIT = 8`,资料片 `0..15`,原版 `0..12`) - `bit 13`:`bWeaponSwitch`(`0x2000`) 2. **14-bit 双字节编码(`Fog.#10085` `FOG_Encode14BitsToString` / `Fog.#10086` `FOG_Decode14BitsFromString`)**: - `statBytes[26] = (nPackedValue & 0x7f) | 0x80`(对应 `nPackedValue` 的 `bits 0..6`) - `statBytes[27] = ((nPackedValue >>> 7) & 0x7f) | 0x80`(对应 `nPackedValue` 的 `bits 7..13`) - 因此在 `statBytes[27]` 中: - `bit 0` 是 `nPackedValue` 的 `bit 7`(`bUnkFlag0x80`) - `bits 1..5` 才是 5 位 `nProgression`,即 `(statBytes[27] >>> 1) & 0x1f` - `bit 6` 是 `bWeaponSwitch` - `bit 7` 是非零掩码 `0x80` ## 影响表现 直接用 `statBytes[27] & 0x1f` 会把 `(progression << 1) & 0x1f` 当作进度值(翻倍后对 32 取模): - Normal Act 3(`progression = 3`, `statBytes[27] = 0x86`)被误判为 `progression = 6`,前端错误解锁噩梦难度按钮; - 刚通关普通难度(`progression = 5`, `statBytes[27] = 0x8a`)被误判为 `progression = 10`,前端错误解锁地狱难度按钮(点击创建地狱房间会被 d2cs 拒绝); - 资料片全通 Matriarch/Patriarch(`progression = 15`, `statBytes[27] = 0x9e`)被解析为 `30`。 ## 修复范围 - `src/netproto/mcp/packets.ts`:`parseCharStatstring` 中将 `progression` 修正为 `(actAndDiff >>> 1) & 0x1f`,并基于 `progression` 计算 `act`。 - `src/client/frontend/flow-state.ts`:`getAllowedDifficultiesForSelectedCharacter` 回退分支修正为 `(statBytes[27]! >>> 1) & 0x1f`,同步更新注释。 - `src/client/frontend/statstring-avatar.ts`:同步修正字节偏移 27 的位域注释。 - 补充并更新单元测试验证 `FOG_Encode14BitsToString` 编码与难度解锁边界。
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.

Dependencies

No dependencies set.

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