The Minecraft Protocol is the set of rules and conventions that define how data is exchanged between the client and server in the Minecraft game. The protocol is used to establish a connection, authenticate users, and exchange game data, such as player movements, chat messages, and block updates.
In this post, we will explore the Minecraft Protocol and learn how to read and write packets to communicate with the Minecraft server.
The Minecraft Protocol uses various data types to represent different values, such as integers, strings, and arrays. Understanding these data types is essential for reading and writing packets correctly.
Here are some common data types used in the Minecraft Protocol:
Name | Size | Encodes | Notes |
---|---|---|---|
Boolean | 1 | true or false | Encoded as 0x00 for false and 0x01 for true . |
Byte | 1 | Signed 8-bit integer | Range from -128 to 127. |
Unsigned Byte | 1 | Unsigned 8-bit integer | Range from 0 to 255. |
Short | 2 | Signed 16-bit integer | Range from -32,768 to 32,767. |
Unsigned Short | 2 | Unsigned 16-bit integer | Range from 0 to 65,535. |
Int | 4 | Signed 32-bit integer | Range from -2,147,483,648 to 2,147,483,647. |
Long | 8 | Signed 64-bit integer | Range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. |
Float | 4 | 32-bit floating-point number | IEEE 754 single-precision floating-point format. |
Double | 8 | 64-bit floating-point number | IEEE 754 double-precision floating-point format. |
Text Component | NBT Tag | Represents a String for plain text or a compound tag for formatted text. | |
JSON Text | JSON string | Encoded as a String with JSON formatting. | |
VarInt | Signed 32-bit integer | Variable-length encoding for integers. | |
VarLong | Signed 64-bit integer | Variable-length encoding for long integers. | |
BitSet | Bit Flags | Encoded as a VarInt length followed by a byte array of bit flags. | |
Fixed BitSet | Fixed-Length Bit Flags | Encoded as a byte array of bit flags with a fixed length. | |
String | UTF-8 string | Encoded as VarInt length followed by UTF-8 bytes. | |
Identifier | String | Encoded as a String with namespace and path. | |
Entity Metadata | NBT Tag | Represents metadata for entities. | |
Slot | Item Stack | Represents an item stack in a slot. | |
NBT Tag | Compound Tag | Represents a compound tag with key-value pairs. | |
Position | 8 | Block Position | Encoded as three 32-bit integers for X, Y, and Z coordinates. |
Angle | 1 | 1/256th of a full turn | Encoded as a byte representing a rotation angle. |
UUID | 16 | Universally Unique Identifier | Encoded as 128 bits for a unique identifier. |
Optional | Optional Value | Encoded as a Boolean flag followed by the value if present. | |
Array | List of Values | Encoded as a VarInt length followed by the array elements. | |
Enum | Enumerated Value | Encoded as a VarInt index for the enum value. | |
Byte Array | Array of Bytes | Encoded as a VarInt length followed by the byte array. |
A VarInt (Variable Integer) is a method of serializing integers using a variable number of bytes. This technique is often used in protocols and file formats to efficiently encode integer values of varying lengths, optimizing the amount of storage or bandwidth required.
VarInt encodes an integer into one or more bytes. The number of bytes used depends on the size of the integer. Each byte has a continuation bit (most significant bit, MSB), which indicates whether the next byte is part of the integer. If the MSB is 1, the next byte is part of the integer. If the MSB is 0, the current byte is the last byte of the integer.
Let's consider encoding and decoding the integer value 300:
Binary Representation:
300 in binary is 100101100
.
Split into 7-bit Groups:
The binary representation is split into two 7-bit groups: 0010110
and 0000100
.
Encoding:
0010110
(binary) -> 0010110
(7 bits) + 1
(MSB) -> 1010110
(binary) -> 0xAC
(hex).0000100
(binary) -> 0000100
(7 bits) + 0
(MSB) -> 0000100
(binary) -> 0x04
(hex).0xAC 0x04
.Decoding:
0xAC
(binary 1010110
), extract 0010110
(value 22
).0x04
(binary 0000100
), extract 0000100
(value 4
).4 << 7
(shift left by 7 bits) + 22
= 300
.Here are some examples of VarInt encoding and decoding:
Value | Hex Bytes | Decimal Bytes |
---|---|---|
0 | 0x00 | 0 |
1 | 0x01 | 1 |
127 | 0x7F | 127 |
128 | 0x80 0x01 | 128 |
255 | 0xFF 0x01 | 255 |
300 | 0xAC 0x04 | 300 |
2097151 | 0xFF 0xFF 0x7F | 2097151 |
2147483647 | 0xFF 0xFF 0xFF 0xFF 0x07 | 2147483647 |
Here is an example of a function to read/write a VarInt from a buffer:
A VarLong (Variable Length Long) is essentially a 64-bit version of a VarInt, which you already understand. Like VarInt, VarLong encodes integers using a variable number of bytes to optimize space.
Let’s encode and decode the integer value 9223372036854775807 (max 64-bit signed integer):
0111111111111111111111111111111111111111111111111111111111111111
0111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1
.0111111
-> 1111111
(7 bits + MSB 1) -> 0xFF
.1
-> 0000001
s (7 bits + MSB 0) -> 0x01
.0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x01
.0xFF
(seven times) -> 0111111
each time, combined by shifting left by 7 bits.0x01
-> 1
.9223372036854775807
.Here are some examples of VarLong encoding and decoding:
Value | Hex Bytes | Decimal Bytes |
---|---|---|
0 | 0x00 | 0 |
1 | 0x01 | 1 |
127 | 0x7F | 127 |
128 | 0x80 0x01 | 128 |
255 | 0xFF 0x01 | 255 |
300 | 0xAC 0x02 | 300 |
2097151 | 0xFF 0xFF 0x7F | 2097151 |
2147483647 | 0xFF 0xFF 0xFF 0xFF 0x07 | 2147483647 |
9223372036854775807 | 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x7F | 9223372036854775807 |
Here is an example of a function to read/write a VarLong from a buffer:
Bit sets in the context of Minecraft protocol represent packed lists of bits used for efficiently storing and transmitting boolean data. There are two types of bit sets: BitSet and Fixed BitSet. Each serves different purposes and has unique encoding schemes.
A BitSet is a dynamically sized array of bits, typically used for more flexible bit storage.
To check if a bit is set:
where 𝑖 is the bit position starting from 0.
Here is an example of a function to read/write a BitSet from/to a buffer:
1. Create our BitSet structure:
2. Read/Write functions:
A Fixed BitSet is a bit set with a fixed number of bits, typically used for storing a specific number of flags or options.
1. Create our Fixed BitSet structure:
To read strings using a variable length encoding system like VarInt, the process generally involves reading the length of the string first (encoded as a VarInt) and then reading that many bytes to get the string data.
Here is an example of a function to read/write a string from/to a buffer:
An Identifier is a unique string that represents a resource in the Minecraft game. It consists of two parts: a namespace and a path. Identifiers are used to reference items, blocks, entities, and other game resources.
namespace:path
minecraft
for vanilla resources and custom namespaces for mods.To read positions encoded in a 64-bit value, split into three signed integer parts (X, Y, Z), you need to understand how these components are packed and unpacked. The position encoding is defined as follows:
To encode the coordinates into a 64-bit value:
To decode the 64-bit value back into X, Y, and Z:
val = read_long();
x = val >> 38;
y = (val << 52) >> 52;
z = (val << 26) >> 38;
These shifts assume arithmetic (signed) shifts to preserve the sign of the coordinates. If your language does not support arithmetic shifts, you need to handle potential negative values explicitly.
Let's consider encoding and decoding the position (100, 64, 300):
1. Define a Position Structure:
2. function to Read a Long (64-bit value):
3. function to Decode a Position:
read_long
reads 8 bytes from the buffer and converts them into a 64-bit integer (i64
).Packets are the fundamental units of data exchanged between the client and server in the Minecraft Protocol. Each packet has a specific purpose and structure defined by its packet ID and data fields. Understanding how to read and write packets is essential for implementing custom functionality in the game.
Field | Size | Description |
---|---|---|
Length | VarInt | Length of the packet data (excluding the length field itself). |
ID | VarInt | Packet ID that identifies the type of packet being sent. |
Data | Byte Array | Packet data fields specific to the packet type. |
Field | Size | Description | Compressed? |
---|---|---|---|
Packet Length | VarInt | Length of the packet data (excluding the length field itself). | No |
Data Length | VarInt | Length of the uncompressed packet data. | No |
ID | VarInt | Packet ID that identifies the type of packet being sent. | Yes |
Data | Byte Array | Packet data fields specific to the packet type. | Yes |
Field | Size | Description | Compressed? |
---|---|---|---|
Packet Length | VarInt | Length of the packet data (excluding the length field itself). | No |
Data Length | VarInt | It's 0 for uncompressed packets. | No |
ID | VarInt | Packet ID that identifies the type of packet being sent. | No |
Data | Byte Array | Packet data fields specific to the packet type. | No |
bc1q4uzvtx6nsgt7pt7678p9rqel4hkhskpxvck8uq
0x7a70a0C1889A9956460c3c9DCa8169F25Bb098af
7UcE4PzrHoGqFKHyVgsme6CdRSECCZAoWipsHntu5rZx