Browse Source

docs: document real game login implementation

wuzilong 1 month ago
parent
commit
5b4a8ecfb7
1 changed files with 152 additions and 0 deletions
  1. 152 0
      cq2_sdk/docs/real-game-login-implementation.md

+ 152 - 0
cq2_sdk/docs/real-game-login-implementation.md

@@ -0,0 +1,152 @@
+# 真实游戏登录实现说明
+
+## 当前状态
+
+高层游戏登录已经实现,不再是占位接口。
+
+公开入口 `LauncherFacade.run_until_enter_game()` 会依次完成:
+
+1. 获取当天开放的区服;
+2. 调用 996 盒子的 `EnterGameClient`;
+3. 下载并解密详细服务器列表;
+4. 解析 TCP 登录地址;
+5. 建立角色服务器连接;
+6. 完成账号认证和角色选择,必要时按调用方明确提供的角色名创建角色;
+7. 重连世界服务器;
+8. 等待服务器返回 `SC2`,确认已经进入游戏世界;
+9. 启动心跳并创建后续游戏任务所需的上下文。
+
+登录成功条件是:服务器先返回 `SC658`,客户端发送 `CS1018`,随后服务器返回 `SC2`。仅建立 TCP 连接或获取角色列表不代表登录成功。
+
+## Python 实现位置
+
+### 高层调用入口
+
+文件:[`src/cq2_sdk/client.py`](../src/cq2_sdk/client.py)
+
+- `LauncherFacade`:组织区服选择、启动、TCP 连接和登录流程。
+- `LauncherFacade.run_until_enter_game()`:一键执行到进入游戏世界。
+- `Cq2Client`:管理盒子登录会话、TCP 连接、游戏会话和任务上下文。
+- `Cq2Client._bind_game_session()`:登录成功后创建移动、战斗、任务组件。
+
+### 游戏登录状态机
+
+文件:[`src/cq2_sdk/login/game.py`](../src/cq2_sdk/login/game.py)
+
+- `GameLogin.login()`:执行完整角色服和世界服登录协议。
+- `GameSession`:保存角色 ID、角色名、会话 ID、用户 ID 和世界就绪状态。
+- `GameLoginError`:保存服务器消息号、错误码和错误说明。
+- 所有发送、接收和重连操作共用一个登录截止时间,避免网络操作无限等待。
+- 每次全新认证前都会把 TCP `sdata` 重置为零;角色服和世界服之间重连时保留服务器下发的新会话值。
+
+### 区服列表解密和地址解析
+
+文件:[`src/cq2_sdk/login/server_list.py`](../src/cq2_sdk/login/server_list.py)
+
+- `decode_detailed_server_list()`:完成 Base64、标记校验、XXTEA 解密和 JSON 解析。
+- `resolve_server()`:展开紧凑区服字段并生成不可变的 `ServerInfo`。
+- `fetch_server_info()`:通过启用 TLS 证书校验的 HTTPS 请求获取详细区服列表。
+- 当区服没有明确 IP 时,按照 `s{main_server_id}-{srvRoot}` 生成服务器地址。
+
+### 登录数据模型
+
+文件:[`src/cq2_sdk/login/models.py`](../src/cq2_sdk/login/models.py)
+
+- `BoxSession`:保存盒子登录阶段返回的会话数据。
+- `ServerInfo`:保存选中区服、主服、地址、端口、代理端口及客户端元数据。
+- `ServerInfo` 为不可变数据类,配置版本映射也不能被外部修改。
+
+### TCP 连接和心跳
+
+文件:[`src/cq2_sdk/transport/tcp.py`](../src/cq2_sdk/transport/tcp.py)
+
+- `TcpClient.connect()`:建立异步 TCP 连接。
+- `TcpClient.send()` 和 `TcpClient.recv()`:发送和接收完整协议帧。
+- `TcpClient.reconnect()`:切换角色服或世界服,并重置消息序列号。
+- `TcpClient.start_heartbeat()`:登录成功后发送 `CS60000` 心跳。
+
+### TCP 帧编解码
+
+文件:[`src/cq2_sdk/transport/framing.py`](../src/cq2_sdk/transport/framing.py)
+
+协议头解码后是 9 个小端 32 位整数:
+
+```text
+param3, msg_id, recog, param1, param2, sdata, codec, body_len, sequence
+```
+
+36 字节明文头经过 Encode6 后形成 48 字节网络头;当前登录流程支持 `codec=0` 的明文消息体。
+
+## 登录协议流程
+
+```text
+CS2001 -> SC529 -> CS104 -> SC530 -> CS100 -> SC520
+                                                |
+                                                +-- 已有角色 -> CS103
+                                                |
+                                                +-- 没有角色且提供 char_name
+                                                    -> CS101 -> SC521 -> CS103
+
+CS103 -> SC525 -> 重连世界服 -> CS20 -> SC658 -> CS1018 -> SC2
+```
+
+主要约束:
+
+- `SC529.recog == 1` 使用新登录分支,否则使用旧登录分支。
+- 角色 ID 始终按字符串保存,避免大整数被截断。
+- 已有角色优先选择 `last == 1` 且未被封禁的角色。
+- 角色列表为空时必须显式传入 `char_name`,SDK 不会自行生成角色名。
+- `SC503`、`SC522`、`SC526` 的错误码来自 `recog`;`SC527` 的错误码来自 `param1`。
+- `SC658` 是发送 `CS1018` 的前置门槛,`SC2` 是进入世界成功的最终依据。
+
+## 调用示例
+
+```python
+import asyncio
+
+from cq2_sdk import Cq2Client
+
+
+async def main() -> None:
+    async with Cq2Client(
+        account="your-account",
+        password="your-password",
+    ) as client:
+        assert client.launcher is not None
+        session = await client.launcher.run_until_enter_game(
+            server_name="玖扬复古176",
+            zone_index=0,
+            # 仅在新区没有现有角色时提供:
+            # char_name="角色名",
+            # job=0,
+            # sex=0,
+            timeout=10.0,
+        )
+        print(session.char_name, session.world_ready)
+
+
+asyncio.run(main())
+```
+
+不要把真实账号、密码或 Token 写入源代码。建议通过进程环境或其他本地安全配置传入,并确保相关文件被 Git 忽略。
+
+## 测试位置
+
+- 完整登录状态机和世界入口门槛:[`tests/unit/test_game_login.py`](../tests/unit/test_game_login.py)
+- 高层客户端编排和资源所有权:[`tests/unit/test_client.py`](../tests/unit/test_client.py)
+- 服务器列表解密与解析:[`tests/unit/test_server_list.py`](../tests/unit/test_server_list.py)
+- TCP 连接、重连和心跳:[`tests/unit/test_tcp_client.py`](../tests/unit/test_tcp_client.py)
+- 帧格式和真实心跳头:[`tests/unit/test_framing.py`](../tests/unit/test_framing.py)
+
+当前完整测试套件为 `288 passed`。此外,公开服务器列表已经在启用 TLS 证书校验的情况下完成在线解析烟测。
+
+## 已知边界
+
+- 真实账号端到端登录可能触发验证码或产生游戏侧会话,因此默认自动测试不执行该步骤。
+- 在线烟测不会隐式创建角色;只有调用者明确提供 `char_name` 时才允许走创建角色分支。
+- 登录后的 13 个新手任务仍属于后续协议开发范围,本文件只说明进入游戏世界之前的登录链路。
+
+## 设计资料
+
+- [真实游戏登录设计](../../docs/superpowers/specs/2026-09-18-real-game-login-design.md)
+- [真实游戏登录实施计划](../../docs/superpowers/plans/2026-09-18-real-game-login.md)