Implement 40250 classic client port

This commit is contained in:
shenlei
2026-09-01 18:49:28 +09:00
parent 2277b1dd60
commit 8f61990a83
481 changed files with 169826 additions and 293 deletions
+53 -7
View File
@@ -4,13 +4,16 @@
# add_child(cs)
# cs.query(host, port, func(map): ...) # map: { port:int -> status:int }
#
# 明文 TCP(无 libsodium):连上任一频道端口 → 发 CG_STATE_CHECKER(0x000F,len 4)
# 跳过其它包直到 GC_RESPOND_CHANNELSTATUS(0x0010) → 读 int32 count → count × {i16 port, u8 status}。
# 明文 TCP(无 libsodium):m2dev 发 CG_STATE_CHECKER(0x000F,len 4)40250 classic
# 发单字节 CG_STATE_CHECKER(206);服务端均返回频道条目。classic 响应是
# [210][int32 count][count × {i16 port, u8 status}]m2dev 则是带 u16 header/length 的包。
# status0 关 / 1 正常 / 2 忙 / 3 满(Metin2 约定,非在线人数——那要改服务器)。
extends Node
const CG_STATE_CHECKER := 0x000F
const GC_RESPOND_CHANNELSTATUS := 0x0010
const CLASSIC_CG_STATE_CHECKER := 206
const CLASSIC_GC_RESPOND_CHANNELSTATUS := 210
const STATUS_TEXT := {0: "关闭", 1: "正常", 2: "拥挤", 3: "爆满"}
@@ -20,11 +23,15 @@ var _peer: StreamPeerTCP
var _cb: Callable
var _deadline := 0.0
var _sent := false
var _classic := false
var _classic_rx := PackedByteArray()
func query(host: String, port: int, callback: Callable = Callable(), timeout_s := 3.0) -> void:
_cb = callback
_peer = StreamPeerTCP.new()
_sent = false
_classic = OS.get_environment("MT_PROTOCOL") == "classic"
_classic_rx = PackedByteArray()
_deadline = _now() + timeout_s
if _peer.connect_to_host(host, port) != OK:
_finish({})
@@ -48,14 +55,53 @@ func _process(_dt: float) -> void:
if st != StreamPeerTCP.STATUS_CONNECTED:
return
if not _sent:
var req := PackedByteArray()
req.resize(4)
req.encode_u16(0, CG_STATE_CHECKER)
req.encode_u16(2, 4)
var req := PackedByteArray([CLASSIC_CG_STATE_CHECKER]) if _classic else PackedByteArray()
if not _classic:
req.resize(4)
req.encode_u16(0, CG_STATE_CHECKER)
req.encode_u16(2, 4)
_peer.put_data(req)
_sent = true
# 攒够一个包就解析
_try_parse()
if _classic:
_try_parse_classic()
else:
_try_parse()
func _try_parse_classic() -> void:
var avail := _peer.get_available_bytes()
if avail > 0:
var r: Array = _peer.get_data(avail)
if r[0] != OK:
_finish({})
return
_classic_rx.append_array(r[1])
# The stock checker reads a byte header, then an int32 count. A regular
# game handshake may arrive while waiting, so discard known fixed packets.
while not _classic_rx.is_empty() and _classic_rx[0] != CLASSIC_GC_RESPOND_CHANNELSTATUS:
var skip := 1
match int(_classic_rx[0]):
0xFE: skip = 13 # GC_HANDSHAKE
0xFC: skip = 1 # GC_TIME_SYNC
if _classic_rx.size() < skip:
return
_classic_rx = _classic_rx.slice(skip)
if _classic_rx.size() < 5:
return
var count := _classic_rx.decode_s32(1)
if count < 0 or count > 4096:
_finish({})
return
var total := 5 + count * 3
if _classic_rx.size() < total:
return
var out := {}
var off := 5
for i in count:
out[_classic_rx.decode_s16(off)] = _classic_rx.decode_u8(off + 2)
off += 3
_finish(out)
func _try_parse() -> void:
var avail := _peer.get_available_bytes()