Introduction
As we all know, the server and client of Minecraft are separated. The client and the server communicate with each other through TCP / IP (especially Java version and UDP version) (so we need to configure the port property of server.properties on the server and the input required for client connection IP:PORT). If we know the specific communication protocol between the client and the server, we can disguise the client to initiate an access request to the server for a series of operations (such as pressure test)
Knowledge points
The knowledge points involved in this section are as follows:
- Parsing and encapsulation of specific protocols
- Socket API
- BIO
explain
As we all know, as of the posting date, the latest version of Minecraft Server has reached 1.18 +. For such a mature project that has carried out countless version iterations for many years, its protocol must also undergo a series of development and changes. Therefore, considering the difficulty of getting started, this paper will introduce the MC C / S communication protocol version from low to high, based on the downward compatibility of MC Server , the higher version server also supports the resolution of lower version clients. Therefore, we use Sugarcane 1.17.1 to complete the test of this chapter with the higher version server.
start
BETA 1.8 - 1.3
Before Minecraft 1.4, if you need to request the server to return the current basic information, you only need to send 0xFE to the server, and the server will return its current status information according to the following protocol:
Field name | Field type | matters needing attention |
---|---|---|
Package ID | Byte | The returned package ID should be: 0xFF |
Field length | Short | The length of the remainder of the packet |
MOTD | A paragraph to UTF-16BE Encoded string | From here on, all fields should be separated by § in the same string. The maximum length of this string is 64 bytes. |
Number of online players | The number of players currently playing on the server | |
Maximum players | Maximum number of players the server can support |
Based on the above, we can write the following program to parse the data packet. Here we give the general tools and methods first
/** * Gets the verified legal string content * @apiNote The packet ID must be 0xFF and the length must be legal * */ protected static String getSecureString(InputStream inputStream, InputStreamReader inputStreamReader) throws IOException { int packetId = inputStream.read(); if (packetId == -1) throw new IOException("Premature end of stream."); if (packetId != 0xFF) throw new IOException("Invalid packet ID (" + packetId + ")."); int length = inputStreamReader.read(); if (length == -1) throw new IOException("Premature end of stream."); if (length == 0) throw new IOException("Invalid string length."); char[] chars = new char[length]; if (inputStreamReader.read(chars, 0, length) != length) throw new IOException("Premature end of stream."); return new String(chars); }
Parsing code
/** * @version BETA - 1.3 * */ private void connect() throws IOException { try ( Socket socket = new Socket() ) { socket.setSoTimeout(TIMEOUT); socket.connect(new InetSocketAddress(host, port), TIMEOUT); try ( OutputStream dataOutputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE); ) { dataOutputStream.write(0xFE); String string = getSecureString(inputStream, inputStreamReader); String[] args = string.split("§"); motd = args[0]; onlinePlayers = Integer.parseInt(args[1]); maxPlayers = Integer.parseInt(args[2]); } } }
The returned data content (sensitive part) has been processed
Raw data (visual data other than package ID and field length)
After analysis
Careful partners can see that data such as motd and the number of online players have been obtained, but some data such as serverVersion have not been captured. Don't worry, these are the elements added to the high version protocol.
1.6
A small partner will ask: why do you talk about 1.6 first? Where are 1.4 and 1.5? The reason is very simple, because the protocols of 1.4 and 1.5 are simplified versions of 1.6 and 1.6 In order to be compatible with the previous version, the Notchian server only accepts the old version of the protocol.
Client to server
For 1.4 + MC client and server TCP connection. It does not perform authentication and login (e.g agreement and Protocol encryption Instead, it sends packets in the following format:
- FE - packet identifier of server list ping
- 01 - payload of server list ping (always 1)
- FA - packet identifier of plug-in message
- 00 0B - the length of the following string, in characters, as a short string (always 11)
- 00 4D 00 43 00 7C 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74 - coded as UTF-16BE String MC|PingHost of string
- XX XX - length of other data, as short. Calculated as, where is the number of bytes in UTF-16BE encoded hostname. 7 + len(hostname)len(hostname)
- XX—Protocol version For example, the last version (74) 4a
- XX - the length of the following string, in characters, as a short string
- ... - the hostname to which the client connects, encoded as UTF-16BE character string
- XX - the port to which the client is connecting, as an integer.
Note: all data types are big endian For downward compatibility, all Notchian servers only care about the first three bytes (and you can only send these three bytes), while the Bukkit server only cares about the first two bytes. After reading, the response will be sent to the client, and all legacy servers (< = 1.6) will respond accordingly. FE 01 FA
Packet example:
0000000: fe01 fa00 0b00 4d00 4300 7c00 5000 6900 ......M.C.|.P.i. 0000010: 6e00 6700 4800 6f00 7300 7400 1949 0009 n.g.H.o.s.t..I.. 0000020: 006c 006f 0063 0061 006c 0068 006f 0073 .l.o.c.a.l.h.o.s 0000030: 0074 0000 63dd .t..c.
Server to client
After the first three bytes, the form is encoded as The data packet of UTF-16BE string starts with ASCII 0167 characters and is used between each element \ 0 as the separator, the specific resolution is as follows:
Field name | Field type | matters needing attention |
---|---|---|
Package ID | Byte | The returned package ID should be: 0xFF |
Field length | Short | The length of the remainder of the packet |
Protocol version | A paragraph to UTF-16BE Encoded string | for example seventy-four |
Server version | as 1.8.7 | |
MOTD | From here on, all fields should be separated by § in the same string. The maximum length of this string is 64 bytes. | |
Number of online players | The number of players currently playing on the server | |
Maximum players | Maximum number of players the server can support |
Parsing code
private void connect() throws IOException { try ( Socket socket = new Socket() ) { socket.setSoTimeout(TIMEOUT); socket.connect(new InetSocketAddress(host, port), TIMEOUT); try ( DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); InputStream inputStream = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE); ) { dataOutputStream.write(new byte[]{(byte) 0xFE, (byte) 0x01}); String string = ServerInfoV1_3.getSecureString(inputStream, inputStreamReader); if (string.startsWith("§")) { String[] data = string.split("\0"); pingVersion = Integer.parseInt(data[0].substring(1)); protocolVersion = Integer.parseInt(data[1]); serverVersion = data[2]; motd = data[3]; onlinePlayers = Integer.parseInt(data[4]); maxPlayers = Integer.parseInt(data[5]); } else { String[] data = string.split("§"); motd = data[0]; onlinePlayers = Integer.parseInt(data[1]); maxPlayers = Integer.parseInt(data[2]); } } } }
Returned data content
Raw data (visual data other than package ID and field length)
After analysis
1.4 - 1.5
Before Minecraft 1.6, the operation from the client to the server was much simpler. Only two bytes of start ID were sent: FE 01
current
After 1.6 +, the connection mode between the client and the server changes.
1. Shake hands
First, the client send status is set to 1 Handshake Data packet.
Packet identification | Field name | Field type | note |
---|---|---|---|
0x00 | Protocol version | VarInt | see also Protocol version number . the version the client plans to use to connect to the server (not important for ping). If the client is pinging to determine the version to use, it should be set by convention. - 1 |
server address | character string | The host name or IP used for the connection, such as localhost or 127.0.0.1. The notqian server does not use this information. Note that the SRV record is completely redirected. For example, if _minecraft._tcp.example.com points to mc.example.org, the user connected to example.com will provide mc.example.org as the server address in addition to connecting to it. | |
Server port | Unsigned short | The default value is 25565. This information is not used by the Notchian server. | |
Next status | VarInt | state Expected 1, but Sign in It can also be 2. |
request
Client follow up request Packet. This packet has no fields.
Packet identification | Field name | Field type | note |
---|---|---|---|
0x00 | No field |
response
The server should use Respond to the response packet Please note that the Notchian server will wait to receive for unknown reasons Ping The packet takes 30 seconds, then times out and sends a response.
Packet identification | Field name | Field type | note |
---|---|---|---|
0x00 | JSON response | character string | See below; As with all strings, this is prefixed with the length of VarInt |
The JSON response field is a JSON Object in the following format:
{ "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>" }
For this version of protocol transmission, we need to use the varInt function specified by Minecraft to convert int to varInt type, so as to construct the correct handshake packet. The conversion code is as follows:
/** * varInt Read function */ protected static int readVarInt(DataInputStream in) throws IOException { int i = 0; int j = 0; while (true) { int k = in.readByte(); i |= (k & 0x7F) << (j++ * 7); if (j > 5) throw new RuntimeException("VarInt too big"); if ((k & 0x80) != 0x80) break; } return i; } /** * varInt Write function * */ protected static void writeVarInt(DataOutputStream out, int paramInt) throws IOException { while (true) { if ((paramInt & ~0x7F) == 0) { out.writeByte(paramInt); return; } out.writeByte(paramInt & 0x7F | 0x80); paramInt >>>= 7; } }
Based on the above, the following parsing code can be given, and the parsing json part will not be repeated:
/** * The format of sending data packet is: packet length + content * */ private void connect() throws IOException { try (Socket socket = new Socket()) { socket.setSoTimeout(9000); socket.connect(new InetSocketAddress(host, port), 9000); try ( DataOutputStream out = new DataOutputStream(socket.getOutputStream()); DataInputStream in = new DataInputStream(socket.getInputStream()); //> Handshake ByteArrayOutputStream handshake_bytes = new ByteArrayOutputStream(); DataOutputStream handshake = new DataOutputStream(handshake_bytes); ) { handshake.writeByte(PACKET_HANDSHAKE); writeVarInt(handshake, packageProtocolVersion); writeVarInt(handshake, host.length()); handshake.writeBytes(host); handshake.writeShort(port); writeVarInt(handshake, PACKET_STATUS_HANDSHAKE); //< Status Handshake writeVarInt(out, handshake_bytes.size()); // Size of packet out.write(handshake_bytes.toByteArray()); //< Status Request out.writeByte(0x01); // Size of packet out.writeByte(PACKET_STATUS_REQUEST); //< Status Response // https://wiki.vg/Protocol#Response readVarInt(in); // Size pingVersion = readVarInt(in); int length = readVarInt(in); byte[] data = new byte[length]; in.readFully(data); String json = new String(data, StandardCharsets.UTF_8); JsonObject jsonObject = new Gson().fromJson(json, JsonObject.class); parseJson(jsonObject); } } }
Parsed data (the original data is in json form, there are a large number of key value pairs, which are too chaotic and will not be given)
last
Minecraft series will be continuously updated, and biological AI, self-made class library and other contents will be updated in the later stage. If you are also an MC fan, you might as well pay attention to a wave~