feat(autotest): 按 zt 回调号自动应答 G.q1wd

- 解码到 LoadDependencies(zt) 后取 X,延迟 300ms 回 G.q1wd(X)(与真实抓包 c2s_0001 逐字节一致)
- 大厅连接保持打开,避免 digest 会话被提前释放
- 实测:链路密钥唯一命中、zt 容器解码、G.q1wd 已被中继识别并转发
This commit is contained in:
TermiNexus committed 2026-10-21 00:50:47 +08:00
1 parent b2deca31be
commit 4ee63a78c9
1 file changed
+22 -19
@@ -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<Int>()
val SpaceAliases = HashMap<Int, Pair<Long, Long>>()
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)") }