diff --git a/TankiOffline/app/src/jsMain/kotlin/App/AutoTest.kt b/TankiOffline/app/src/jsMain/kotlin/App/AutoTest.kt index 518edf8..e847f32 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/AutoTest.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/AutoTest.kt @@ -19,7 +19,7 @@ import Net.Protocol.Entry import Net.Protocol.EntryEvent import Net.Protocol.S2cDecoder import Net.Protocol.SpaceFrame -import Net.Protocol.XorCipher +import Net.Protocol.ZtPayload // ws 包的客户端(可自定义握手头;Node 内置 WebSocket 不能改 User-Agent) // 用 module.require 动态取构造器,避开 CommonJS/UMD 的 import 形态差异 @@ -81,8 +81,7 @@ object AutoTest { var PendingDigest: ByteArray? = null var AssignedSpace: Long? = null var SpaceTarget = 0L - var C2sCipher: XorCipher? = null - var ObjectDataSent = false + val PendingReplies = ArrayList() val SpaceAliases = HashMap>() println("TankiOffline 自动测试:大厅=$LobbyUrl 空间=$SpaceUrl(请求空间 $WantSpace,超时 ${TimeoutMs}ms)") @@ -112,6 +111,12 @@ object AutoTest { println(" ⚠ methodId=${Rec.MethodId} 回编码不一致") } if (Rec.MethodId in Containers) S.Containers++ + // LoadDependencies(zt) 带回调号 X → 客户端必须回 G.q1wd(X),服务端才会继续下发 pt + val V = Rec.Value + if (Rec.MethodId == 3216143066888387731L && V is ZtPayload) { + PendingReplies.add(V.X) + println(" ↳ 排入自动应答 G.q1wd(X=${V.X})") + } } } @@ -160,17 +165,6 @@ object AutoTest { S.Messages++ val Bytes = js("new Int8Array(Data)") as ByteArray val Bodies = Decoder.Feed(Bytes) { Line -> println(" $Line") } - // 第一条空间帧到达(此时密钥已知)后再发 G.q1wd;太早发服务端会直接关连接。 - // 之后的上行必须用 c2s 方向的同一密钥(密钥表再 ^87)。 - val K = Decoder.Key - if (IsSpace && !ObjectDataSent && K != null) { - val C = XorCipher(K, true) - C2sCipher = C - val Req = C.Apply(Entry.BuildObjectDataRequest(SpaceTarget)) - SendBytes(Req) - ObjectDataSent = true - println("→ [空间] G.q1wd(1)(已加密,K=$K)") - } for (B in Bodies) { S.Frames++ if (IsSpace) { @@ -201,14 +195,23 @@ object AutoTest { } } if (Reconnect && PendingDigest != null) { - println("→ 关闭大厅连接,改连空间服务器") - try { - Ws.close() - } catch (_: Throwable) { - } + // 注意:**不要关大厅连接**。digest 与会话绑定,关掉大厅连接后再进空间, + // 服务端只推首帧就会正常关闭(实测 code=1000)。让大厅连接闲置即可。 + println("→ 保持大厅连接,另开空间连接") Open(SpaceUrl, true) } } + // 本批空间帧解完后,把待回的回调号发出(c2s 方向实测全程明文,见抓包 c2s_0001..) + // 真实抓包里 G.q1wd 在 s2c_0000 之后约 300ms 才发,这里给一点处理间隔 + if (IsSpace && PendingReplies.isNotEmpty()) { + while (PendingReplies.isNotEmpty()) { + val X = PendingReplies.removeAt(0) + SetTimeout({ + SendBytes(Entry.BuildObjectDataRequest(SpaceTarget, X)) + println("→ [空间] G.q1wd($X)(明文)") + }, 300) + } + } } val OnError: (dynamic, dynamic) -> Unit = { _, _ -> println("✗ WebSocket 错误($Url)") }