如何用Python Socket实现离线Minecraft服务器的Bot连接及数据包交互?
问题解答:Minecraft离线服务器Bot连接实现
核心结论:离线模式无需加密流程
连接离线Minecraft服务器时,完全不需要处理加密密钥请求(0x01包)和加密响应,服务器会跳过所有加密、认证步骤,直接返回登录成功数据包(0x02)。你的理解是对的,这一步不需要额外的加密交互。
现有数据包代码检查
你写的握手和登录开始数据包逻辑方向正确,但有一个关键细节缺失:Minecraft的数据包要求整个数据包的长度要先用VarInt编码后放在最前面,你当前的代码只编码了包ID(0x00),没有加上整个包的长度前缀,这会导致服务器无法正确解析数据包。
修正后的握手包发送逻辑:
def send_handshake(sock_con, host, port, version): # 构造包内容(不含长度前缀) packet_content = struct.pack(">B", 0x00) + varint.encode(version) + \ varint.encode(len(host)) + host.encode('utf-8') + \ struct.pack(">H", port) + struct.pack(">B", 0x02) # 计算长度并编码为VarInt,作为数据包前缀 packet_length = varint.encode(len(packet_content)) # 发送完整数据包(长度前缀 + 包内容) full_packet = packet_length + packet_content print("Handshake Packet Bytes ", full_packet) sock_con.send(full_packet)
登录开始包同理,需要添加长度前缀:
def send_login_start(sock_con, name): username_bytes = name.encode('utf-8') packet_content = struct.pack(">B", 0x00) + varint.encode(len(username_bytes)) + username_bytes packet_length = varint.encode(len(packet_content)) full_packet = packet_length + packet_content print("Login Packet Bytes ", full_packet) sock_con.send(full_packet)
接收并解析登录成功数据包
发送完握手和登录开始包后,需要从socket读取服务器返回的数据包,流程如下:
- 先读取VarInt格式的数据包总长度
- 根据长度读取完整的数据包内容
- 从内容中读取VarInt格式的包ID:登录成功包的ID是
0x02 - 解析包内容:包含UUID(离线模式下为基于用户名生成的伪UUID)和用户名
示例接收代码:
def read_varint(sock): # 实现Minecraft VarInt读取逻辑 result = 0 for i in range(5): byte = ord(sock.recv(1)) result |= (byte & 0x7F) << (7 * i) if not byte & 0x80: break return result def read_varint_from_bytes(data): # 从字节数组读取VarInt的辅助函数 result = 0 ptr = 0 for i in range(5): byte = data[ptr] ptr += 1 result |= (byte & 0x7F) << (7 * i) if not byte & 0x80: break return result def receive_login_success(sock_con): # 读取数据包长度 packet_length = read_varint(sock_con) packet_content = sock_con.recv(packet_length) # 读取包ID ptr = 0 packet_id = 0 while True: byte = packet_content[ptr] ptr += 1 packet_id |= (byte & 0x7F) << (7 * (ptr - 1)) if not byte & 0x80: break # 处理登录成功包 if packet_id == 0x02: # 解析UUID uuid_length = read_varint_from_bytes(packet_content[ptr:]) ptr += len(varint.encode(uuid_length)) uuid = packet_content[ptr:ptr+uuid_length].decode('utf-8') ptr += uuid_length # 解析用户名 username_length = read_varint_from_bytes(packet_content[ptr:]) ptr += len(varint.encode(username_length)) username = packet_content[ptr:ptr+username_length].decode('utf-8') print(f"登录成功:UUID={uuid}, 用户名={username}") return True else: # 收到加密请求包,说明服务器是在线模式 print("服务器要求加密,无法用离线账号连接") return False
后续保活与连接维持
登录成功进入Play状态后,服务器会定期发送保活数据包(0x00包),你需要在收到后原样返回相同的包内容,否则服务器会主动断开连接。
整体流程总结
- 创建TCP Socket连接到服务器的
host:port - 发送带长度前缀的握手数据包
- 发送带长度前缀的登录开始数据包
- 读取服务器返回的数据包,确认登录成功
- 进入Play状态,循环处理保活包及其他游戏数据包
内容的提问来源于stack exchange,提问作者TheGoldenMine
相关产品推荐
相关产品推荐

