Implement 40250 classic client port
This commit is contained in:
@@ -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 的包。
|
||||
# status:0 关 / 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()
|
||||
|
||||
Reference in New Issue
Block a user