Files
agentmc/tests/test_protodef.py

105 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""验证 protodef 解释器能真跑协议——用真实握手字节。"""
import sys, os, json, struct
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mcfold.protodef import ProtoDef, Reader, Writer, ProtoError, for_phase
DATA='/home/newqqagent/plugin_data/mc/runtime/node_modules/minecraft-data/minecraft-data/data/pc/1.20/protocol.json'
def load():
d=json.load(open(DATA))
return d
def pd_for(d, phase, side):
return for_phase(d, phase, side)
def test_varint_roundtrip():
"""varint 是 MC 最核心的原语;含负数回绕。"""
load()
for v in [0,1,127,128,255,300,25565,2097151,-1,-2147483648,2147483647]:
w=Writer(); w.varint(v)
got=Reader(w.getvalue()).varint()
assert got==v, f"varint {v} -> {got}"
print(" ✓ varint 往返(含负数/边界)")
def test_pstring():
load()
for s in ["", "abc", "中文名字", "x"*300]:
w=Writer(); w.pstring(s)
assert Reader(w.getvalue()).pstring()==s
print(" ✓ pstring 往返(含 UTF-8/长串)")
def test_real_handshake():
"""构造真实的 handshake+login_start 字节并解出(最硬的检验)。"""
d=load()
pd=pd_for(d,'handshaking','toServer')
# handshaking toServer packet_set_protocol
w=Writer()
w.varint(0x00) # packet id (handshaking)
w.varint(763) # protocol version = 1.20.1
w.pstring("127.0.0.1") # server address
w.write(struct.pack(">H", 25565)) # port
w.varint(2) # next state = login
raw=w.getvalue()
r=Reader(raw)
pkt=pd.read(d['handshaking']['toServer']['types']['packet'], r)
assert pkt['name']=='set_protocol', pkt
p = pkt['params'] # switch 的参数落在 params 下
assert p['protocolVersion']==763, p
assert p['serverHost']=='127.0.0.1', p
assert p['serverPort']==25565, p
assert p['nextState']==2, p
assert r.remaining()==0, f"有残留字节: {r.remaining()}"
print(f" ✓ 真实 handshake 解码: proto={p['protocolVersion']} host={p['serverHost']}:{p['serverPort']} nextState={p['nextState']}")
def test_login_success_decode():
"""packet_success: uuid + username + properties(真实形状)。"""
d=load()
pd=pd_for(d,'login','toClient')
w=Writer()
w.varint(0x02) # login toClient packet_success id
w.write(bytes(range(16))) # uuid
w.pstring("BotName")
w.varint(0) # properties count = 0
r=Reader(w.getvalue())
pkt=pd.read(d['login']['toClient']['types']['packet'], r)
assert pkt['name']=='success', pkt
p = pkt['params']
assert p['username']=='BotName', p
assert p['properties']==[], p
print(f" ✓ login success 解码: uuid={p['uuid'][:8]}… username={p['username']}")
def test_play_packet_mapper():
"""play 阶段 111 个包由 mapper 分发 —— 验证能正确路由。"""
d=load()
pd=pd_for(d,'play','toClient')
w=Writer(); w.varint(0x00)
# 0x00 = bundle_delimiter(无字段)
r=Reader(w.getvalue())
pkt=pd.read(d['play']['toClient']['types']['packet'], r)
assert pkt['name']=='bundle_delimiter', pkt
print(f" ✓ play 包路由: 0x00 -> {pkt['name']}")
# 0x10 = declare_commands(我们做 mod 命令发现要用的包)
w2=Writer(); w2.varint(0x10)
r2=Reader(w2.getvalue())
try:
pkt2=pd.read(d['play']['toClient']['types']['packet'], r2)
print(f" ✓ declare_commands 路由: 0x10 -> {pkt2.get('name')}")
except ProtoError as e:
print(f" ~ declare_commands 需要更多字节(预期,因为只喂了 id): {str(e)[:40]}")
def test_no_overshoot():
"""越界必须报错而不是静默读错 —— 这是我实测 mineflayer 静默失真的对照。"""
d=load()
pd=pd_for(d,'login','toClient')
r=Reader(b"\x05") # varint 说 5 字节字符串,实际没有
try:
pd.read("string", r); assert False, "应当抛错"
except ProtoError as e:
print(f" ✓ 越界显式报错(不静默): {str(e)[:50]}")
if __name__=="__main__":
for fn in [test_varint_roundtrip, test_pstring, test_real_handshake,
test_login_success_decode, test_play_packet_mapper, test_no_overshoot]:
fn()
print("\nprotodef 解释器:全部通过")