real-game-login-implementation.md 6.4 KB

真实游戏登录实现说明

当前状态

高层游戏登录已经实现,不再是占位接口。

公开入口 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

  • LauncherFacade:组织区服选择、启动、TCP 连接和登录流程。
  • LauncherFacade.run_until_enter_game():一键执行到进入游戏世界。
  • Cq2Client:管理盒子登录会话、TCP 连接、游戏会话和任务上下文。
  • Cq2Client._bind_game_session():登录成功后创建移动、战斗、任务组件。

游戏登录状态机

文件:src/cq2_sdk/login/game.py

  • GameLogin.login():执行完整角色服和世界服登录协议。
  • GameSession:保存角色 ID、角色名、会话 ID、用户 ID 和世界就绪状态。
  • GameLoginError:保存服务器消息号、错误码和错误说明。
  • 所有发送、接收和重连操作共用一个登录截止时间,避免网络操作无限等待。
  • 每次全新认证前都会把 TCP sdata 重置为零;角色服和世界服之间重连时保留服务器下发的新会话值。

区服列表解密和地址解析

文件: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

  • BoxSession:保存盒子登录阶段返回的会话数据。
  • ServerInfo:保存选中区服、主服、地址、端口、代理端口及客户端元数据。
  • ServerInfo 为不可变数据类,配置版本映射也不能被外部修改。

TCP 连接和心跳

文件: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

协议头解码后是 9 个小端 32 位整数:

param3, msg_id, recog, param1, param2, sdata, codec, body_len, sequence

36 字节明文头经过 Encode6 后形成 48 字节网络头;当前登录流程支持 codec=0 的明文消息体。

登录协议流程

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 是进入世界成功的最终依据。

调用示例

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 忽略。

测试位置

当前完整测试套件为 288 passed。此外,公开服务器列表已经在启用 TLS 证书校验的情况下完成在线解析烟测。

已知边界

  • 真实账号端到端登录可能触发验证码或产生游戏侧会话,因此默认自动测试不执行该步骤。
  • 在线烟测不会隐式创建角色;只有调用者明确提供 char_name 时才允许走创建角色分支。
  • 登录后的 13 个新手任务仍属于后续协议开发范围,本文件只说明进入游戏世界之前的登录链路。

设计资料