2V1-d: offline login -> select -> loading against a fake 40250 server

port.login_flow drives the real 40250 root scripts through the ported
CPythonNetworkStream and CAccountConnector against a loopback auth + game
server built from net/classic (wire layouts, sequence table, cipher) that
answers like the 40250 desc.cpp/input*.cpp: handshake, key agreement,
LOGIN3/AUTH_SUCCESS, LOGIN2, the character list, DirectEnter reconnect,
CHARACTER_SELECT, the loading packets and CLIENT_VERSION, with every
sequence byte checked.

- NetStream::ActivateCipher decrypts ciphertext already buffered behind
  KEY_AGREEMENT_COMPLETED (PORT)
- CPythonApplication is final (PORT): no application object exists on the
  platform yet, so its virtual overrides must be called directly
- app.GetTime (GetGlobalTime) and camera/center-position stubs
- SendClientVersionPacket verbatim in the PhaseGame stand-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 15:20:32 +09:00
co-authored by Claude Opus 5.5
parent 4d9b800efb
commit 8d9d863c7e
10 changed files with 753 additions and 5 deletions
+1 -1
View File
@@ -411,7 +411,7 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
| 2V1-a | `EterLib/NetStream`、`NetAddress`、`NetDevice`、`NetPacketHeaderMap`、`EterBase/cipher.cpp` | 完成:五个文件 `port_copy` 原样(含 32768 字节序列表、`_IMPROVED_PACKET_ENCRYPTION_` 的 Crypto++ DH2 + CTR 路径);`shim/win32/winsock.h` 把 Winsock 名字映射到 BSD socket,并补齐两处语义差异:`select` 忽略 nfds,connect 失败的 socket 不算可写(Winsock 只在 exceptfds 报,40250 靠超时判失败);非阻塞 connect 的 `EINPROGRESS` 报成 `WSAEWOULDBLOCK`;`WSAStartup` 忽略 SIGPIPE。`port.net` 用 loopback 覆盖连接、收发、对端断开、拒绝连接超时和双方密钥协商后的加解密 |
| 2V1-b | `PythonNetworkStream` + HandShake/Login/Select/Loading phase + `AccountConnector` | 完成:`PythonNetworkStream{,Event,PhaseHandShake,PhaseLogin,PhaseSelect,PhaseLoading,PhaseOffline}.cpp`、`AccountConnector.cpp`、`InsultChecker.cpp`、`AffectFlagContainer.cpp` 原样 `port_copy`;`Packet.h` 里所有 `long` 字段改为 32 位 `LONG`(PORT 标注),`port.packet` 把登录/选角/Loading 用到的 38 个包的尺寸与已在真服 1:1 验证过的 `net/classic/wire_classic.h` 比对(两头宏冲突,分两个编译单元);`PythonPackModule.cpp` 的登录密钥函数恢复编译;`Locale.cpp` 的 `LocaleService_Is*`/安全密钥部分原样放进 platform `PythonApplication.cpp`;`WiseLogicXTrap` 取 40250 无 XTRAP 分支,`ProcessCRC` 为桩(魔方只用于 PhaseGame 攻击包)。链接闭包里尚未移植的逻辑单元(`PythonPlayer`、`NetworkActorManager`、`FlyingObjectManager`、`GuildMark*`、`MarkManager`、PhaseGame*/`PythonGuild` 的部分成员)放在 `extension/src/platform/pending/<Lib>/` 作为替身,移植对应单元的提交里删除;Loading 阶段会收到的 CharacterUpdate/PointChange/ItemSet/QuickSlotAdd 替身只吃掉包体,保证读流不断。`port_gate.sh macos` 改为构建 `all`(此前 ctest 会跑门禁没构建的测试),port-only 配置补 `find_library(iconv)` |
| 2V1-c | `net` 模块(`PythonNetworkStreamModule.cpp`)替换 2V0-e 的桩;`app.Create`/`Process` 接网络 | 完成:`PythonNetworkStreamModule.cpp` 原样移植,`GameplayModules.cpp` 的 `net` 桩删除,`PythonBoot` 按 40250 `CPythonApplication` 成员顺序创建 `CPythonNetworkStream`/`CAccountConnector`/`CPythonPlayer`/`CFlyingManager`/`CEffectManager` 并调用 `initnet()`;`app.Create` 创建 `CNetworkDevice`,`Process()` 每帧先 `CPythonNetworkStream::Process` 再 `CAccountConnector::Process`(公会标记上传/下载的 Process 随 `GuildMark*` 移植)。`python_host_test.gd` 断言 `net` 不再是桩(`PHASE_WINDOW_SELECT == 2`、未连接) |
| 2V1-d | FakeServer 离线跑完 HandShake→Login→Select→Loading | 待做 |
| 2V1-d | FakeServer 离线跑完 HandShake→Login→Select→Loading | 完成:`port.login_flow` 用真实 40250 root 脚本(intrologin/introselect/introloading/networkmodule)驱动移植的 `CPythonNetworkStream` + `CAccountConnector`,对接 `tests/port_login_flow_server.cpp` 的回环 auth + game 假服(用已在真服 1:1 验证过的 `net/classic` 包布局、序列表与 cipher 按 40250 `desc.cpp`/`input*.cpp` 应答,与客户端不共享代码):握手 bias/重试 + 密钥协商 → `CG_LOGIN3`/`GC_AUTH_SUCCESS` → `CG_LOGIN2` → `GC_EMPIRE`/PHASE_SELECT/`GC_LOGIN_SUCCESS_NEWSLOT` 到 `SelectCharacterWindow` → `StartGame()` 3 秒后 `DirectEnter` 按槽位地址重连、再登录、直入 `LoadingWindow` 发 `CG_CHARACTER_SELECT` → PHASE_LOADING/`GC_MAIN_CHARACTER`/`GC_CHARACTER_POINTS` → 客户端回 `CG_CLIENT_VERSION(2)`;假服逐包校验序列字节。修正:`NetStream::ActivateCipher` 解密 COMPLETED 后已缓冲的密文(PORT,40250 靠服务器 pulse 间隔规避此竞争);`CPythonApplication` 加 `final`(PORT,platform 无应用对象,虚函数需直接调用),补 `GetGlobalTime`(`app.GetTime`)与 `SetCenterPosition`/`SetEventCamera`/`SetDefaultCamera` 桩;`SendClientVersionPacket` 原样放进 PhaseGame 替身。偏差:`GetExcutedFileName` 仍为桩(版本包文件名为空)。Loading 结束后 `net.StartGame()` 打开 `game.GameWindow` 属 2V2 |
| 2V1-e | 真服 smoke(凭据只从环境变量读) | 待做 |
3. **2V2 — GamePhase/角色显示**:加入 GamePhase 的最小分派闭包、`PythonCharacterManager`、`InstanceBase`、`ActorInstance`
及角色模型/动画/资源 adapter。验收为进入游戏、创建主角并显示可辨认的静止角色;这个切片要求 2D 的 proto 和所需资源通过