Classic Protocol

The protocol for Minecraft Classic is a little messy, though far less of a mess than the multiplayer protocols for all of the versions after Classic 0.30.

A CC0-licensed Classic protocol server written in Python.

The community has designed numerous extensions for things like defining custom block types, custom entity models, client messages longer than 64 bytes, and more.

More information on CPE (Note: this is written a bit ambiguously and I had to trial-and-error some details for certain extensions when implementing them.)

Vanilla

All integers are big-endian.

A signed 11-5 fixed point format is used for entity positions. Dividing these by 32 gives you the true fractional position.

Unsigned bytes are used for player angles. When scaled, 0–255 equals 0-2π.

Strings are space-padded 64-byte long ASCII or IBM CP437 byte strings.

00 Handshake

The client sends a handshake packet immediately after connecting. The server immediately responds back with a handshake packet.

The client-to-server handshake packet's `passkey` field may be a plaintext password or a special key generated by the server browser to ensure the player is connecting with a legitimate username.

struct client_handshake {
    opcode: u8 = 0x00,
    version: u8 = 0x07,
    username: [64]u8,
    passkey: [64]u8,
    cpe_support: u8,    // 0x42 in clients with CPE support, 0 otherwise
}
struct server_handshake {
    opcode: u8 = 0x00,
    version: u8 = 0x07,
    title: [64]u8,
    motd: [64]u8,       // Used by some clients for cheat settings
    operator: u8,       // 0x64 if the connecting client
                        // is an operator, 0x00 otherwise
}

01 Ping

The server occasionally sends this packet to each connected client to make sure the client-server connection is still open.

struct ping {
    opcode: u8 = 0x01,
}

02 Level Change

When recieved, the client unloads the current level and prepares for incoming level data.

struct level_change {
    opcode: u8 = 0x02,
}

03 Level Data

The sent level data is a one-dimensional unsigned byte array of length `(x size * y size * z size)`.

The level data is prefixed with a big-endian signed 32-bit integer representing the total byte size of the map, then GZip compressed and sent in pieces.

Each Level Data packet sends a 1024-byte long piece of the compressed data, with pieces smaller than 1024 bytes being padded with 0x00 bytes.

struct level_data {
    opcode: u8 = 0x03,
    used_len: i16,  // How many of the 1024 bytes are used
    data: [1024]i8,
    unused: u8,
}

04 Level Size

Sent after the final Level Data packet, the server informs the client of the dimensions of the map.

struct level_size {
    opcode: u8 = 0x04,
    x_size: i16,
    y_size: i16,
    z_size: i16,
}

05 Client Block

Sent by the client when placing or deleting a block. The client assumes its block updates are valid if it does not recieve a response 06 Server Block Update at the same block coordinate.

struct client_block {
    opcode: u8 = 0x05,
    x: i16,
    y: i16,
    z: i16,
    place: u8,    // 1 if placing a block, 0 if removing
    block_id: u8, // Always whatever block the client is holding,
                  // even when deleting blocks
}

06 Server Block

Sent by the server to all clients when a block is updated.

struct server_block {
    opcode: u8 = 0x06,
    x: i16,
    y: i16,
    z: i16,
    block_id: u8,
}

07 Create Player

Sent from the server to all clients to add a player's visible model to the world.

If the player ID field is set to 0xFF, it indicates the recieving client is allowed to switch from a loading screen to the playable game state.

It is unknown if player ID 0xFF is a special case or if any number above 0x7F will trigger the same behavior.

If sent with player ID 0xFF to a client that is already playing, this acts like 08 Player Move and teleports the player.

struct create_player {
    opcode: u8 = 0x07,
    eid: i8,
    name: [64]u8,   // The name shown on the player's model
                    // and the player list, not required to
                    // be the player's login username
    x: i16,
    y: i16,
    z: i16,
    yaw: u8,
    pitch: u8,
}

08 Player Move

Sent by the client every 1/20th of a second.

Sent by the server to move the player models each client sees, or if sent with a player ID of 0xFF, the client is teleported.

struct player_move {
    opcode: u8 = 0x08,
    eid: i8,        // Unused on the client, used by some clients to
                    // represent the current block ID the player is holding
    x: i16,
    y: i16,
    z: i16,
    yaw: u8,
    pitch: u8,
}

09, 0A, 0B Relative Movement

These packets are not required for client or server operation. They should only be implemented if you are building a client targeting compatibility with servers that send these packets. The client can only ever send 08 Player Move.

A player will be moving and rotating at the same time often enough to render these practically useless for saving bytes.

struct relative_move_rot {
    opcode: u8 = 0x09,
    eid: i8,
    xdelta: i8, //! signed 3-5 fixed point
    ydelta: i8,
    zdelta: i8,
    yaw: u8,    //! sets the rotation, is not a delta update
    pitch: u8,
}
struct relative_move {
    opcode: u8 = 0x0a,
    eid: i8,
    xdelta: i8, //! signed 3-5 fixed point
    ydelta: i8,
    zdelta: i8,
}
struct relative_rot {
    opcode: u8 = 0x0b,
    eid: i8,
    yaw: u8,   //! absolute angles, not a delta
    pitch: u8,
}

0C Remove Player

The server sends this packet to all clients to remove a player model from the world, typically because someone disconnected.

struct remove_player {
    opcode: u8 = 0x0c,
    eid: i8,
}

0D Chat Message

Sent by the client to send a chat message or command to the server.

Sent by the server to one or all clients to add a line of text to the client's chat area.

The player ID field is unused when the client sends this packet. Some clients use this field to mark if a message has more parts to recieve.

struct chat_message {
    opcode: u8 = 0x0d,
    eid: i8,
    data: [64]u8,
}

0E Kick

Sent by the server to forcefully disconnect a client with a message.

struct kick {
    opcode: u8 = 0x0e,
    message: [64]u8,
}