- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
128 lines
16 KiB
Markdown
128 lines
16 KiB
Markdown
# network.security_encoding
|
||
|
||
## Scope
|
||
|
||
完整核对 40250 客户端的握手时间基准、后续 time sync、sequence 表和 `bSeq` 规则、`_IMPROVED_PACKET_ENCRYPTION_` 的 DH2/CTR、未启用 improved encryption 时的 TEA 安全分支、HybridCrypt/pack 注入、locale codepage、定长字符串以及连接清理。判断标准是调用链、分支条件、状态转移顺序、时钟/资源来源和失败副作用是否一致;“当前端有同名算法”不等于已对齐。
|
||
|
||
## Reference call chain
|
||
|
||
- `UserInterface/PythonNetworkStreamPhaseHandShake.cpp`
|
||
- `CPythonNetworkStream::HandShakePhase`
|
||
- `CPythonNetworkStream::RecvHandshakePacket`
|
||
- `CPythonNetworkStream::RecvHandshakeOKPacket`
|
||
- `CPythonNetworkStream::RecvHybridCryptKeyPacket`
|
||
- `CPythonNetworkStream::RecvHybridCryptSDBPacket`
|
||
- `CPythonNetworkStream::RecvKeyAgreementPacket`
|
||
- `CPythonNetworkStream::RecvKeyAgreementCompletedPacket`
|
||
- `UserInterface/PythonNetworkStreamPhaseLogin.cpp`, `PythonNetworkStreamPhaseSelect.cpp`, `PythonNetworkStreamPhaseGame.cpp`
|
||
- 各阶段 `HEADER_GC_HANDSHAKE`、`HEADER_GC_HANDSHAKE_OK`、HybridCrypt 和 key-agreement allowlist;每个 packet handler 完成后立即从该阶段处理函数返回。
|
||
- `UserInterface/AccountConnector.cpp`
|
||
- auth connector 的重复 handshake、ping/sequence、legacy security、登录成功后的 Panama key 和 game stream 切换。
|
||
- `EterLib/NetStream.cpp`
|
||
- `CNetworkStream::SetSecurityMode`、`IsSecurityMode`、`SendSequence`、`__SendInternalBuffer`、`Clear`、`Connect`、`ActivateCipher`。
|
||
- `EterBase/cipher.cpp`, `cipher.h`
|
||
- DH2 Prepare/Agree、算法选择、密钥/IV 派生和 CTR stream 状态。
|
||
- `EterBase/tea.cpp`
|
||
- 未定义 `_IMPROVED_PACKET_ENCRYPTION_` 时的收发 TEA 缓冲区及 8-byte 对齐。
|
||
- `EterLocale/StringCodec.cpp`、`UserInterface/Locale*.h/.cpp`
|
||
- Windows `WideCharToMultiByte`/`MultiByteToWideChar`、CP1258 特殊处理和 locale codepage 来源。
|
||
|
||
40250 的具体时钟语义:初始 handshake 设置全局 `ELTimer_SetServerMSec(dwTime + lDelta)`,回显 `dwTime + 2*lDelta`、`lDelta=0`,之后调用 `CTimer::SetBaseTime()`;Select/Game 的 handshake 保存 `m_kServerTimeSync`,发送 `HEADER_CG_TIME_SYNC` 并追加 sequence;收到 `HEADER_GC_HANDSHAKE_OK` 后用收到 blank 的本地耗时重新设置全局 server msec。这个全局 owner 和二次校准不能用“本地 stream 能计算一个相对时间”替代。
|
||
|
||
## Current call chain
|
||
|
||
- `extension/src/net/classic/classic_stream.cpp/.h`
|
||
- `handle_control` 处理 handshake、`HDR_GC_TIME_SYNC`、key agreement/completed、ping、phase;`server_frame_ms` 用 `steady_clock` 与每个 `ClassicStream` 的两个基准值计算。
|
||
- `emit_bytes` 在进入发送队列时立即加密;`flush_send` 只负责写 socket。
|
||
- `extension/src/net/classic/classic_cipher.cpp/.h`
|
||
- 当前实现包含 40250 DH2/CTR 所需的 RFC 5114 参数、算法 selector、key/IV 派生和双向 CTR 状态。
|
||
- `extension/src/net/classic/sequence_table.h`, `wire_classic.h`
|
||
- 内置 32768-byte sequence 表和 `is_sequence_cg` 规则。
|
||
- `extension/src/net/classic/classic_session.cpp`, `extension/src/net/m2_client.cpp`
|
||
- phase/版本发送、time-sync mode 和 Godot 网络生命周期。
|
||
- `extension/src/net/secure_cipher.cpp/.h`
|
||
- 另一套 libsodium X25519/XChaCha20-Poly1305 backend;它不是 40250 `EterBase::Cipher` 的替代实现,不能作为 classic improved-encryption 的等价证据。
|
||
- `extension/src/net/text_codec.h/.cpp`
|
||
- classic fixed text now crosses one explicit UTF-8 -> GB2312/GBK wire boundary;
|
||
fixed fields are capped after conversion and decoded back to UTF-8.
|
||
|
||
## Branch matrix
|
||
|
||
| 场景 | 40250 完整行为 | 当前端实际行为 | 结论 |
|
||
|---|---|---|---|
|
||
| 初始 handshake | 读取固定包,设置全局 `ELTimer`,回显 `time+2*delta`/`delta=0`,调用 `CTimer::SetBaseTime`;auth connector 也有自己的同类路径 | 保存 `m_server_time_base`/`m_client_time_base`,回显字节基本一致;没有全局 `ELTimer`/`CTimer` owner,auth/game 也没有统一时钟服务 | `PARTIAL`:wire 主体接近,时间副作用和 owner 不同 |
|
||
| Select/Game 后续 handshake | 保存 `m_kServerTimeSync`,发 `0xFC + handshake + sequence`;收到 blank `HANDSHAKE_OK` 后按 `changeClientTime` 到达时间重新设置全局 server clock | `ClassicStream` 暂存后续 handshake 的 server/client 基准,发 `0xFC`/sequence;收到 blank 后才提交二次校准 | `PARTIAL`:stream 语义已对齐,仍缺少全局 clock owner/跨 connector fixture |
|
||
| improved DH2/CTR | `Prepare`→`Activate`→发送固定 key-agreement reply→收到 completed 后 `ActivateCipher`;completed 前后的缓冲和状态顺序由 `CNetworkStream` 控制 | `ClassicCipher` 的 DH2/selector/派生公式接近 reference;stream 也实现 reply/completed/已缓冲密文解密 | `PARTIAL`:算法可做双端 round-trip,但没有 40250 server fixture、固定边界/失败分支和真实分片互操作证据 |
|
||
| improved 发送时点 | 明文先进入 send buffer,`__SendInternalBuffer` 在真正 flush 时按当前 security mode 加密 | `emit_bytes` 在排队时立即加密,随后再 flush | `PARTIAL`:通常字节顺序相同,但队列、重复 flush、切换 security mode 和部分写时点不是同一语义 |
|
||
| legacy TEA | 未定义 improved 时 `SetSecurityMode` 保存独立收发 key;接收按 8-byte 缓冲解密,发送按 8-byte 对齐/缓存后发送;auth/game 均可启用 | classic 侧没有完整的 `SetSecurityMode`/TEA 收发缓冲调用链;`classic_cipher` 中的 TEA 只是可选 DH2 algorithm selector,不是 legacy stream mode | `PARTIAL`:分支没有实现为同一协议路径 |
|
||
| HybridCrypt keys/SDB | handshake、login、select、game 阶段均允许动态包;消费固定头+精确 stream 长度,并注入 `CEterPackManager` | header 在 wire table 中可识别,但 classic session/parser 没有等价 pack manager 注入,当前会拒绝或忽略该可选资源分支 | `PARTIAL`:动态 framing 不是完整功能等价 |
|
||
| Panama pack/login key | auth 成功按 `loginKey XOR g_adwEncryptKey[0..3]` 解密 pack IV,设置 login key,再连接 game stream | 当前登录/资源主链没有 40250 Panama pack IV 解密和同一 auth→game 交接证据 | `PARTIAL`:资源/密钥副作用缺失或未证明 |
|
||
| anti-cheat/HackShield/XTrap | 40250 handshake/game allowlist 有条件编译的 HS/XTrap request/response 分支 | header/尺寸部分存在,但没有 Windows anti-cheat provider 或完整 response 处理 | `PARTIAL`:条件分支未等价 |
|
||
| sequence table | `SEQUENCE_TABLE_SIZE=32768`,`SendSequence` 在 bSeq 开启后逐包取表项、到末尾回 0;bSeq 来自 packet registration | 当前内置 32768 表并按 `is_sequence_cg` 添加 trailer;代码注释声称 verbatim,但还没有和 40250 源数组的 hash/逐项生成校验 | `PARTIAL`:主路径存在,证据和注册矩阵未闭合 |
|
||
| 版本包 | 根据 locale/region 选择 `CG_CLIENT_VERSION2` 或 `CG_CLIENT_VERSION`,固定字段按 Windows locale 字节复制 | 当前已按 `m_locale_is_europe` 选择两个包,字段走 UTF-8 `to_wire` | `PARTIAL`:header/分支存在,非 ASCII 字节语义仍不同 |
|
||
| locale 转码 | `LocaleService_GetCodePage` + Windows codepage 转换;中文固定字段使用 GB2312-compatible bytes;固定字段本质是 byte array/`strncpy` | classic 使用 Windows CP936 或 POSIX iconv 的 GB2312/GBK 转码;Godot/Native 内部仍保持 UTF-8;其它 locale codepage 尚未接入 | `PARTIAL`:40250 中文固定字段已对齐,非中文 locale 仍不同 |
|
||
| fixed string/NUL | reference 对固定 `char[]` 以字节 cap、NUL 结尾和 struct zero-fill 为准;不可表示字符遵循 Win32 codepage 策略 | classic `to_wire()` 在 GB2312 字节上限后截断且结构体 zero-fill;`wire_text_fits()` 按编码后字节拒绝超长/不可表示值;无系统 codec 的平台拒绝高位字节而不泄漏 UTF-8 | `PARTIAL`:中文字段已对齐,fallback/其它 locale 仍需真实 fixture |
|
||
| cleanup/failure | `Clear` 清 cipher、socket、TEA buffer、recv/send positions 和 sequence;key agreement/发送失败按阶段返回并由上层断开 | `disconnect` 清 stream/cipher/buffer/sequence;缺少 legacy buffer、global timer、HybridCrypt/pack 和 auth connector 分层清理矩阵 | `PARTIAL` |
|
||
|
||
## Equivalence matrix
|
||
|
||
| 项目 | 状态 | 证据 |
|
||
|---|---|---|
|
||
| Preconditions | `PARTIAL` | classic 对固定长度、DH2 key-ready、completed 顺序有保护;legacy/hybrid/anti-cheat 和阶段 allowlist 不完整 |
|
||
| Branch structure | `PARTIAL` | initial/time-sync/improved 主分支存在;TEA、HybridCrypt、Panama、anti-cheat 分支没有完整等价实现 |
|
||
| Algorithms/formulas | `PARTIAL` | `ClassicCipher` 的 DH2/CTR 公式与参数接近 reference;缺少 40250 server 密文 fixture,且 legacy TEA stream 没有主链 |
|
||
| State transition order | `PARTIAL` | completed 后激活和已有缓冲解密存在;global clock 二次校准、auth/game 双 connector、flush 时加密时点不同 |
|
||
| Constants/units | `PARTIAL` | handshake/header/32768 sequence 常量已定位;locale byte cap、TEA block/buffer 和 optional packet size 规则未闭合 |
|
||
| Timing/event sources | `PARTIAL` | 当前每个 stream 使用 `steady_clock`,并在 `HANDSHAKE_OK` 提交暂存基准;40250 使用全局 `ELTimer`,auth/game 共用时钟 owner 仍不同 |
|
||
| Resource/data sources | `PARTIAL` | key/sequence/version 来源已定位;当前没有真实 locale codepage 和 EterPack HybridCrypt/Panama 注入来源 |
|
||
| Protocol side effects | `PARTIAL` | handshake/key reply/ping/version 有路径;clock、pack IV、HybridCrypt 和 anti-cheat side effect 缺失或未证明 |
|
||
| Interruption/failure/cleanup | `PARTIAL` | classic disconnect 会清基本状态;TEA 对齐缓存、auth connector 和可选资源的失败/重入/断开组合未覆盖 |
|
||
|
||
## Evidence already available
|
||
|
||
- `extension/tests/net_classic_cipher_test.cpp`:client/server polarity 的 DH2 round-trip、CTR 连续性和新会话密钥差异。
|
||
- `extension/tests/net_classic_encstream_test.cpp`:handshake、key agreement、completed、已缓冲密文和 sequence 的 stream happy path。
|
||
- `extension/tests/net_text_codec_test.cpp`:12 个中文字符在 24 个 GB2312 字节内 round-trip、13 个字符被拒绝、真实中文名称和 32 字节商店招牌边界。
|
||
- `extension/tests/net_classic_session_test.cpp`:角色创建/改名包验证实际写入 GB2312 字节、NUL 结尾和 24 字节边界。
|
||
- `extension/src/net/classic/sequence_table.h`:32768 项静态表和编译期长度断言;`audit_sequence_table.py` 已逐字节比对 40250 数组,SHA-256 为 `3f6f31964896e712f1f54cb813c7b488b656f700f6ca948ece3313419f876279`。
|
||
|
||
## Deep audit round 2026-09-20
|
||
|
||
本轮重新检查 `ClassicStream::handle_control/emit_bytes`、`ClassicCipher`、sequence 表和文本边界,并重跑当前加密/编码回归:
|
||
|
||
- `net_classic_cipher_test`、`net_classic_encstream_test` 和 `net_text_codec_test` 均退出码 0;它们证明当前 DH2/CTR round-trip、连续 CTR、key agreement/已缓存密文、sequence trailer 和 UTF-8 codepoint-safe 截断没有回归。
|
||
- 当前 `HANDSHAKE_OK` 已提交后续 handshake 的暂存 server clock;`server_frame_ms()` 仍是每个 `ClassicStream` 的 steady-clock 相对计算,尚未接入 40250 `CTimer/ELTimer` 全局 owner。`net_classic_stream_test` 已覆盖 blank 前不跳时钟、blank 后完成校准。
|
||
- 当前 `emit_bytes()` 在进入发送队列时加密,参考端在真正 flush internal buffer 时按当前 security mode 加密;在正常单次发送中可能得到相同字节,但切换 security mode、部分写、重复 flush 和失败清理的状态边界不同。
|
||
- `classic_cipher` 中的 TEA 仅作为可选算法 selector,当前 classic stream 没有 40250 legacy `SetSecurityMode` 的独立收发 TEA block buffer 主链;HybridCrypt/SDB、Panama pack IV、HS/XTrap 和其它 locale codepage 也没有完整注入/消费路径。中文 40250 fixed text 已由 `text_codec.cpp` 在网络边界转为 GB2312/GBK-compatible bytes。
|
||
- 32768 项 sequence 表已通过 reference 数组逐字节校验;主要 `bSeq` 分支仍依赖当前 wire 注册表,未完成所有版本/locale/可选包的生成式矩阵。
|
||
|
||
结论:改进加密的局部算法和当前 wire framing 可用,但全局时钟、加密时点、legacy/optional security 分支、codepage 和 sequence 参考数据仍未闭合,合同保持 `PARTIAL`。
|
||
|
||
## Required tests before claiming parity
|
||
|
||
- 使用 40250 wire fixture 验证初始 handshake、Select/Game handshake、`HANDSHAKE_OK` 二次校准和实际 server-frame 时钟;当前已有 socket-free 二次校准回归。
|
||
- 用 40250 server polarity fixture 验证 DH2/CTR:split recv/send、flush 前后加密、重复 key agreement、completed 前后明文、非法长度、失败清理和 reconnect。
|
||
- 保持 40250 sequence 数组逐字节校验,并补齐从 `PacketInfo`/`CMainPacketHeaderMap` 生成的完整 `bSeq` 矩阵。
|
||
- 为 CP949/CP1252/CP1258 等非中文部署建立 reference byte fixtures;中文 GB2312 已覆盖不可表示字符、NUL、byte cap、固定字段 zero-fill 和反向 decode。
|
||
- 对 HybridCrypt keys/SDB、Panama pack、legacy TEA、HS/XTrap 明确支持/拒绝策略,并分别验证不乱 consume、不把包流错位、不产生错误的成功状态。
|
||
|
||
## Status
|
||
|
||
`PARTIAL`。
|
||
|
||
本轮已修复后续 handshake 的二次时钟校准,并加入 40250 sequence table 逐字节审计;DH2/CTR 的局部算法相似性仍不能覆盖全链路差异,在补齐全局 clock、locale、legacy/optional 分支和真实 wire fixture 前,合同保持 `PARTIAL`。
|
||
|
||
## Implementation fix round 2026-09-21 — Chinese fixed-field wire encoding
|
||
|
||
按 40250 中文客户端的 `LocaleService_GetCodePage`/固定 `char[]` 语义补齐文本边界:Godot 和 native 业务层继续以 UTF-8 保存文本,`text_codec.cpp` 在 classic 发送前按 Windows CP936 或 POSIX GB2312/GBK 转码;`wire_text_fits()` 在转码后按字节容量校验,`to_wire()` 只写完整的 GB2312 字符并依靠 zero-initialized packet 保留 NUL。接收路径用同一 codepage 解回 UTF-8。没有系统 codepage codec 的平台不会把高位 UTF-8 字节直接发送到 40250 字段,而是将该值标记为不可表示。
|
||
|
||
角色创建、改名以及聊天、密语、公会、商店等 classic 固定/动态文本发送入口均使用这个边界;地图名、任务名和系统提示仍按各自动态包长度处理,不套用 24 字节角色名限制。
|
||
|
||
回归:`net_text_codec_test`、`net_classic_session_test`、`net_classic_stream_test`、`net_loopback_test`、CTest 23/23、Godot 生命周期/角色选择/移动回归和 `git diff --check` 通过。中文角色名实际创建/改名包已证明 12 个汉字占 24 个 GB2312 字节,13 个汉字被拒绝。合同仍保持 `PARTIAL`,因为 legacy TEA、可选安全分支、其它 locale codepage 和真实服务端中文建号包序仍未完全闭合。
|
||
|
||
## Implementation fix round 2026-09-21
|
||
|
||
按 40250 `RecvHandshakePacket`/`RecvHandshakeOKPacket` 修复 `ClassicStream`:Select/Game 后续 handshake 只保存 pending server/client 基准,发送 `CG_TIME_SYNC` 后等待一字节 `HANDSHAKE_OK`,再按到达耗时提交 server clock;断开时清理 pending 状态,避免下次连接复用旧校准。新增 `audit_sequence_table.py`,对 40250 `EterLib/NetStream.cpp` 的 32768 项 sequence table 与当前静态表逐字节比较,当前两端 SHA-256 完全一致。
|
||
|
||
回归:`net_classic_stream_test`、`net_classic_session_test`、sequence table audit 和 `git diff --check` 通过。全局 `ELTimer/CTimer` owner、legacy TEA、HybridCrypt/Panama/HS-XTrap、Windows codepage、flush 时加密时点和真实 server fixture 仍保持 `PARTIAL`。
|