MineCraft协议——握手篇(讲解+实现)
前几天开服和朋友联机。配置端口转发。不知道什么毛病,好像没有起作用的样子。为了确认到底有没有收到客户端发过来的包,抄了一段代码来监听发过来的TCP报文。
看着收到的数据陷入沉思,不禁好奇MC是怎么握手的。于是去了解了一下

上图是客户端握手时传来的数据。这篇文章的最后将以此为案例分析其中的内容。
参考资料:
[1]http://mcprotocol.orecraft.cn/Server_List_Ping
[2]http://mcprotocol.orecraft.cn/Protocol
一、与服务器会话的状态
mc客户端与服务器的会话,可能有这么几种状态。
Handshaking 正在握手
Status 查询服务器状态
Login 正在登入
Play 正在进行游戏
和服务器建立连接后,总是进入握手状态,握手过程中,可以选择接下来进入Status状态或者Login状态,登录成功后,将进入Play状态。
二、常用的字段及和编码格式
1、定长整数
定长整数:有 Byte,Sort,Int,Long 和 UnsignedByte,UnsignedSort,UnsignedInt,UnsignedLong。
Byte用一字节编码,sort两字节,int四字节,long八字节,均采用大端方式排列,即表示高有效位的字节排在前面,第一位是符号位,负数用补码。
UnsignedByte,UnsignedSort,UnsignedInt,UnsignedLong。为无符号整数,没有符号位。
想必学过几门编程语言的人对上述的数据类型不会陌生
2、变长整数
变长整数:VarInt,VarLong。
VarInt用于编码int类型,占1~5个字节,每个字节用低七位来编码数字。最高一位如果为1,表示还有下一字节。最高位为0,表示没有下一字节了。表示低有效位的字节排在前面,表示高有效位的字节排在后面。
符号位无需单独处理,不把符号位看成符号位就行
举例:
-1 = 0x FF FF FF FF 转成 VarInt: 0xFF FF FF FF 0F
1 = 0x 00 00 00 01 转成VarInt: 0x 01
127 = 0x00 00 00 7F 转成VarInt:0x7F
128 = 0x00 00 00 80 转成VarInt: 0x80 01
VarInt表示比较小的整数时很节约字节,对于比较大的整数也能表示。但是对于任何负数,它始终会占用5个字节
VarLong用于编码long类型,转化方法同理
在参考资料[1]中有例程可以参考
3、字符串
字符串由两部分组成,一个VarInt前缀,表示字符串按UTF-8编码之后的长度,接下来是用UTF-8编码的字符串本身
举例:
“FML” 按utf-8编码->0x46 4D 4C->长度为三字节,3按VarInt编码:0x03->完整的字符串:0x03 46 4D 4C
三、包格式
mc将要发送的数据封装成包(packet)再发送给服务器或客户端

包序号(Packet ID),或者更准确的叫包ID,表示这个包的类型,这个包是干什么的,有哪些字段。注意,包可分为发往客户端的包和发往服务器的包两类。这两类包的id是分开编的,也就是说发往服务器的id=0的包与发往客户端id=0的包意义完全不一样。
数据,由字段依次连接而成,具体包含哪些字段取决于包ID。
四、mc握手和SLP
通过Server List Ping (SLP)可以得到服务器的有关信息(有几个玩家在线,服务器的名字版本号,装了哪些mod)和连接的时延
具体过程如下:
1、客户端发送Handshake包

例子:
00
D4 02
0E 31 32 37 2E 30 2E 30 2E 31 00 46 4D 4C 00
27 66
02
分行写只是为了看着清楚
包ID=0,对应第一行
字段
协议号VarInt:本文章基于mc 1.12.2 协议号340,对应第二行D4 02
服务器地址String:字符串,ip或者域名,比如127.0.0.1,对应第三行。测试用的forge客户端,实际发送的是“127.0.0.1\0FML\0” \0表示00,ASCII码空字符。FML显然是forge mod loader的缩写。
端口号UnsignedSort:默认25565,对应第四行 27 66。
下一个状态VarInt:只能是1或者2。1表示进入Status状态,2表示进入Login状态,例子中是02,要获取服务器信息则应该填01
2、客户端发Request包

没啥好说的
对应的二进制应该是0x0100。第一个字节是包长度,第二个字节包id,数据部分没有
3、服务器回应Response包

一个字段,string,json格式的服务器信息
例如:
{
"version": { "name": "1.8.7", "protocol": 47 },
"players": { "max": 100, "online": 5, "sample": [ { "name": "thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20" } ] },
"description": { "text": "Hello world" },
"favicon": "data:image/png;base64,<data>"
}
4、此时可以发送ping包用来测试服务器时延

五、协议实现
我对mc的一部分协议做了非常好的封装,提供了简洁的API可以调用,并且写了一个SLP的demo
这部分代码可以从我的gitHub上克隆

如图,提供了很好用的API,欢迎大家跟我一起hacking MC的协议。
先定一个小目标,做出可以登上服务器挖矿的工具人!
GitHub仓库:
https://github.com/newtoncy/mcProtocol