MineCraft協(xié)議——握手篇(講解+實(shí)現(xiàn))
前幾天開服和朋友聯(lián)機(jī)。配置端口轉(zhuǎn)發(fā)。不知道什么毛病,好像沒有起作用的樣子。為了確認(rèn)到底有沒有收到客戶端發(fā)過來的包,抄了一段代碼來監(jiān)聽發(fā)過來的TCP報(bào)文。
看著收到的數(shù)據(jù)陷入沉思,不禁好奇MC是怎么握手的。于是去了解了一下

上圖是客戶端握手時(shí)傳來的數(shù)據(jù)。這篇文章的最后將以此為案例分析其中的內(nèi)容。
參考資料:
[1]http://mcprotocol.orecraft.cn/Server_List_Ping
[2]http://mcprotocol.orecraft.cn/Protocol
一、與服務(wù)器會(huì)話的狀態(tài)
mc客戶端與服務(wù)器的會(huì)話,可能有這么幾種狀態(tài)。
Handshaking 正在握手
Status 查詢服務(wù)器狀態(tài)
Login 正在登入
Play 正在進(jìn)行游戲
和服務(wù)器建立連接后,總是進(jìn)入握手狀態(tài),握手過程中,可以選擇接下來進(jìn)入Status狀態(tài)或者Login狀態(tài),登錄成功后,將進(jìn)入Play狀態(tài)。
二、常用的字段及和編碼格式
1、定長(zhǎng)整數(shù)
定長(zhǎng)整數(shù):有 Byte,Sort,Int,Long 和 UnsignedByte,UnsignedSort,UnsignedInt,UnsignedLong。
Byte用一字節(jié)編碼,sort兩字節(jié),int四字節(jié),long八字節(jié),均采用大端方式排列,即表示高有效位的字節(jié)排在前面,第一位是符號(hào)位,負(fù)數(shù)用補(bǔ)碼。
UnsignedByte,UnsignedSort,UnsignedInt,UnsignedLong。為無符號(hào)整數(shù),沒有符號(hào)位。
想必學(xué)過幾門編程語言的人對(duì)上述的數(shù)據(jù)類型不會(huì)陌生
2、變長(zhǎng)整數(shù)
變長(zhǎng)整數(shù):VarInt,VarLong。
VarInt用于編碼int類型,占1~5個(gè)字節(jié),每個(gè)字節(jié)用低七位來編碼數(shù)字。最高一位如果為1,表示還有下一字節(jié)。最高位為0,表示沒有下一字節(jié)了。表示低有效位的字節(jié)排在前面,表示高有效位的字節(jié)排在后面。
符號(hào)位無需單獨(dú)處理,不把符號(hào)位看成符號(hào)位就行
舉例:
-1 = 0x FF FF FF FF 轉(zhuǎn)成 VarInt: 0xFF FF FF FF 0F
1 = 0x 00 00 00 01 轉(zhuǎn)成VarInt:?0x 01
127 = 0x00 00 00?7F?轉(zhuǎn)成VarInt:0x7F
128 = 0x00 00 00?80?轉(zhuǎn)成VarInt: 0x80 01
VarInt表示比較小的整數(shù)時(shí)很節(jié)約字節(jié),對(duì)于比較大的整數(shù)也能表示。但是對(duì)于任何負(fù)數(shù),它始終會(huì)占用5個(gè)字節(jié)
VarLong用于編碼long類型,轉(zhuǎn)化方法同理
在參考資料[1]中有例程可以參考
3、字符串
字符串由兩部分組成,一個(gè)VarInt前綴,表示字符串按UTF-8編碼之后的長(zhǎng)度,接下來是用UTF-8編碼的字符串本身
舉例:
“FML” 按utf-8編碼->0x46 4D 4C->長(zhǎng)度為三字節(jié),3按VarInt編碼:0x03->完整的字符串:0x03?46 4D 4C
三、包格式
mc將要發(fā)送的數(shù)據(jù)封裝成包(packet)再發(fā)送給服務(wù)器或客戶端

包序號(hào)(Packet ID),或者更準(zhǔn)確的叫包ID,表示這個(gè)包的類型,這個(gè)包是干什么的,有哪些字段。注意,包可分為發(fā)往客戶端的包和發(fā)往服務(wù)器的包兩類。這兩類包的id是分開編的,也就是說發(fā)往服務(wù)器的id=0的包與發(fā)往客戶端id=0的包意義完全不一樣。
數(shù)據(jù),由字段依次連接而成,具體包含哪些字段取決于包ID。
四、mc握手和SLP
通過Server List Ping?(SLP)可以得到服務(wù)器的有關(guān)信息(有幾個(gè)玩家在線,服務(wù)器的名字版本號(hào),裝了哪些mod)和連接的時(shí)延
具體過程如下:
1、客戶端發(fā)送Handshake包

例子:
00?
D4 02
0E 31 32 37 2E 30 2E 30 2E 31 00 46 4D 4C 00
27 66?
02
分行寫只是為了看著清楚
包ID=0,對(duì)應(yīng)第一行
字段
協(xié)議號(hào)VarInt:本文章基于mc?1.12.2 協(xié)議號(hào)340,對(duì)應(yīng)第二行D4 02
服務(wù)器地址String:字符串,ip或者域名,比如127.0.0.1,對(duì)應(yīng)第三行。測(cè)試用的forge客戶端,實(shí)際發(fā)送的是“127.0.0.1\0FML\0” \0表示00,ASCII碼空字符。FML顯然是forge mod loader的縮寫。
端口號(hào)UnsignedSort:默認(rèn)25565,對(duì)應(yīng)第四行 27 66。
下一個(gè)狀態(tài)VarInt:只能是1或者2。1表示進(jìn)入Status狀態(tài),2表示進(jìn)入Login狀態(tài),例子中是02,要獲取服務(wù)器信息則應(yīng)該填01
2、客戶端發(fā)Request包

沒啥好說的
對(duì)應(yīng)的二進(jìn)制應(yīng)該是0x0100。第一個(gè)字節(jié)是包長(zhǎng)度,第二個(gè)字節(jié)包id,數(shù)據(jù)部分沒有
3、服務(wù)器回應(yīng)Response包

一個(gè)字段,string,json格式的服務(wù)器信息
例如:
{ ??
?"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、此時(shí)可以發(fā)送ping包用來測(cè)試服務(wù)器時(shí)延

五、協(xié)議實(shí)現(xiàn)
我對(duì)mc的一部分協(xié)議做了非常好的封裝,提供了簡(jiǎn)潔的API可以調(diào)用,并且寫了一個(gè)SLP的demo
這部分代碼可以從我的gitHub上克隆

如圖,提供了很好用的API,歡迎大家跟我一起hacking MC的協(xié)議。
先定一個(gè)小目標(biāo),做出可以登上服務(wù)器挖礦的工具人!
GitHub倉庫:
https://github.com/newtoncy/mcProtocol