Expand description
§mc_protocol
Rust implementation of the Minecraft Java Edition network protocol primitives: serialization, packet framing, encryption, and compression.
§Feature flags
| Feature | Description | Default |
|---|---|---|
async | Async I/O via Tokio | yes |
encryption | AES-128-CFB8 encryption via OpenSSL | no |
compression | Zlib packet compression via flate2 | yes |
To opt in to the encryption feature:
[dependencies]
mc_protocol = { version = "2.1.0", features = ["encryption"] }To disable all default features:
[dependencies]
mc_protocol = { version = "2.1.0", default-features = false }§Modules
| Module | Contents |
|---|---|
prelude | Re-exports of the most commonly used types and traits |
varint | VarInt and VarLong |
ser | Serialize and Deserialize traits, all primitive impls, RawBytes |
packet | RawPacket and UncompressedPacket framing |
compression | Zlib helpers — requires compression feature |
encryption | AES-128-CFB8 types — requires encryption feature |
num | Integer trait for big-endian fixed-width primitives |
§Quick start
use mc_protocol::varint::VarInt;
use mc_protocol::ser::{Serialize, Deserialize};
use std::io::Cursor;
let mut buf = Vec::new();
VarInt(300).serialize(&mut buf).unwrap();
let v = VarInt::deserialize(&mut Cursor::new(&buf)).unwrap();
assert_eq!(v.0, 300);§Derive macro
#[derive(Packet)] generates Serialize and Deserialize
for a struct. When #[packet(ID)] is also present it additionally generates an
associated PACKET_ID: i32 constant and a PacketId impl.
The attribute is optional — omit it for nested/helper structs that appear inside
another packet’s fields (e.g. Vec<T>) and only need serialization.
use mc_protocol::{Packet, varint::VarInt};
#[derive(Packet, Debug)]
#[packet(0x00)]
struct Handshake {
protocol_version: VarInt,
server_address: String,
server_port: u16,
next_state: VarInt,
}
assert_eq!(Handshake::PACKET_ID, 0x00);§Serialization
All primitive types implement Serialize and Deserialize.
Supported types out of the box: bool, i8–i128, u8–u128, f32, f64,
VarInt, VarLong, String, Uuid,
Option<T> (boolean presence flag), Vec<T> (VarInt-prefixed), and
RawBytes (no-prefix byte slice).
All fixed-width integers are big-endian; VarInt/VarLong use little-endian 7-bit groups.
use mc_protocol::ser::{Serialize, Deserialize};
use mc_protocol::varint::VarInt;
use std::io::Cursor;
// VarInt(300) encodes to two bytes
let mut buf = Vec::new();
VarInt(300).serialize(&mut buf).unwrap();
assert_eq!(buf, &[0xac, 0x02]);
// Option<T> — prefixed with a boolean presence byte
let mut buf = Vec::new();
Some(42u32).serialize(&mut buf).unwrap();
let v = Option::<u32>::deserialize(&mut Cursor::new(&buf)).unwrap();
assert_eq!(v, Some(42));
// Vec<T> — prefixed with a VarInt element count
let mut buf = Vec::new();
vec![1u8, 2u8, 3u8].serialize(&mut buf).unwrap();
let v = Vec::<u8>::deserialize(&mut Cursor::new(&buf)).unwrap();
assert_eq!(v, &[1, 2, 3]);§Packet framing
The protocol wraps every packet in a length-prefixed frame.
RawPacket is the on-wire frame (length VarInt + data).
UncompressedPacket is the decoded form (packet ID + payload bytes).
§Sync
use mc_protocol::packet::{RawPacket, UncompressedPacket};
use std::net::TcpStream;
let mut stream = TcpStream::connect("127.0.0.1:25565").unwrap();
// Read an incoming packet
let raw = RawPacket::read_sync(&mut stream).unwrap();
let packet = raw.as_uncompressed().unwrap();
println!("id=0x{:02X} payload_len={}", packet.packet_id, packet.payload.len());
// Write a packet
let up = UncompressedPacket::new(0x00, vec![0x00]);
up.write_sync(&mut stream).unwrap();§Async (requires async feature)
use mc_protocol::packet::{RawPacket, UncompressedPacket};
use tokio::net::TcpStream;
let mut stream = TcpStream::connect("127.0.0.1:25565").await?;
let raw = RawPacket::read_async(&mut stream).await?;
let packet = raw.as_uncompressed()?;
let up = UncompressedPacket::new(0x00, vec![0x00]);
up.write_async(&mut stream).await?;§Compression (requires compression feature)
After a Set Compression packet, frames carry a data_length field.
Packets below the threshold are sent as-is (data_length = 0);
larger packets are zlib-compressed.
use mc_protocol::packet::UncompressedPacket;
let up = UncompressedPacket::new(0x26, vec![0u8; 512]);
let threshold = Some(256);
let raw = up.to_raw_packet_compressed(threshold).unwrap();
let decoded = raw.uncompress(threshold).unwrap();
assert_eq!(decoded.packet_id, 0x26);§Encryption (requires encryption feature)
After a successful login handshake, both sides encrypt every byte with AES-128-CFB8 using the negotiated shared secret as both the key and the IV.
§Sync wrappers
use mc_protocol::encryption::{Cfb8Encryptor, Cfb8Decryptor};
let key: [u8; 16] = [0u8; 16]; // replace with negotiated shared secret
let mut enc = Cfb8Encryptor::new(&key).unwrap();
let mut dec = Cfb8Decryptor::new(&key).unwrap();
let ciphertext = enc.encrypt(b"packet data").unwrap();
let plaintext = dec.decrypt(&ciphertext).unwrap();
assert_eq!(plaintext, b"packet data");For transparent stream encryption, wrap a Read/Write with
Cfb8ReadHalf and Cfb8WriteHalf.
§Async stream (requires async + encryption features)
use mc_protocol::encryption::Cfb8Stream;
use tokio::net::TcpStream;
let stream = TcpStream::connect("127.0.0.1:25565").await?;
let key: [u8; 16] = [0u8; 16];
let mut encrypted = Cfb8Stream::new_from_tcp(stream, &key)?;
// All I/O through `encrypted` is transparently encrypted/decryptedModules§
- compression
- Zlib packet compression as used in the Minecraft Java Edition protocol.
- encryption
- AES-128-CFB8 stream encryption as used by the Minecraft Java Edition protocol.
- num
- Big-endian integer trait used by the protocol serialization layer.
- packet
- Packet framing for the Minecraft Java Edition protocol.
- prelude
- Convenient re-exports for everyday use of
mc_protocol. - ser
- Core serialization and deserialization traits used throughout the protocol.
- varint
- VarInt and VarLong types as defined in the Minecraft Java Edition protocol.
Derive Macros§
- Packet
- Derives
Packetbehaviour for a struct.