Files
flayerproxy/protocol.md
2026-05-20 18:20:59 +02:00

18 KiB

Minecraft Server: Login Flow & World Synchronization

This document outlines the detailed protocol sequences, state transitions, and network synchronization mechanisms used in the Minecraft server, based on the codebase in serversSrc.


1. Connection Handshake & Protocol Transition

When a client initiates a connection to a Minecraft server, it starts in the Handshake protocol. This is handled by ServerHandshakePacketListenerImpl.

sequenceDiagram
    autonumber
    participant Client
    participant Server (Handshake)
    participant Server (Login)
    participant Mojang Session Service
    participant Server (Config)

    Client->>Server (Handshake): ClientIntentionPacket (Intention=LOGIN, protocolVersion)
    Note over Server (Handshake): Validates protocol version
    Server (Handshake)->>Server (Login): Instantiate ServerLoginPacketListenerImpl
    Client->>Server (Login): ServerboundHelloPacket (name)
    alt Offline Mode
        Server (Login)->>Server (Login): startClientVerification (Offline UUID)
    else Online Mode
        Server (Login)-->>Client: ClientboundHelloPacket (ServerID, public key, challenge)
        Client->>Server (Login): ServerboundKeyPacket (encrypted shared secret, encrypted challenge)
        Note over Server (Login): Decrypts secret & sets up AES encryption
        Server (Login)->>Mojang Session Service: hasJoinedServer(username, digest, IP)
        Mojang Session Service-->>Server (Login): GameProfile (UUID, textures)
    end
    Note over Server (Login): verifyLoginAndFinishConnectionSetup
    Server (Login)-->>Client: ClientboundLoginCompressionPacket (optional)
    Server (Login)-->>Client: ClientboundLoginFinishedPacket
    Client->>Server (Login): ServerboundLoginAcknowledgedPacket
    Server (Login)->>Server (Config): Switch protocol, instantiate ServerConfigurationPacketListenerImpl

Protocol Steps:

  1. Client Intention: The client sends a ClientIntentionPacket indicating its target state:
    • STATUS: The client is pinging the server for info (MOTD, online players).
    • LOGIN/TRANSFER: The client wants to connect to the game server.
  2. Version Verification: The server verifies that the client's protocol version matches the server's current version:
    • If mismatched, the server sends a ClientboundLoginDisconnectPacket and closes the socket.
    • If matching, the server transitions the connection to the Login protocol state and spawns a ServerLoginPacketListenerImpl.

2. Login Protocol Phase

The login protocol handles authentication, encryption setup, and duplicate connection handling.

Step-by-Step Flow:

  1. Hello: The client sends its username inside a ServerboundHelloPacket.
  2. Authentication Determination:
    • Offline Mode: The server bypasses encryption/auth, creates an offline UUID profile, and starts verification.
    • Online Mode: The server transitions to the KEY state and sends a ClientboundHelloPacket containing a random challenge token, the server's public key, and server ID.
  3. Encryption Setup:
    • The client generates a shared secret symmetric key (AES), encrypts it and the challenge token using the server's RSA public key, and sends it back in a ServerboundKeyPacket.
    • The server decrypts the shared secret and challenge token using its private key. It verifies the challenge matches.
    • Symmetric AES encryption is initialized on the network socket (connection.setEncryptionKey(...)).
  4. Session Verification:
    • The server computes a SHA-1 hash (server ID + shared secret + server public key) and sends a request to Mojang's session servers to verify if the client has successfully authenticated their session (hasJoinedServer).
    • If verified, the server receives the client's official GameProfile (UUID, username, skin textures, etc.).
  5. Verifying and Compression:
    • The server verifies if the player is allowed to connect (checks UserBanList, IpBanList, UserWhiteList, and server full limitations via PlayerList.canPlayerLogin).
    • If a compression threshold is configured in server.properties, the server sends a ClientboundLoginCompressionPacket and turns on network compression.
    • Duplicate Connection Check: The server disconnects any existing players with the same UUID.
  6. Finished Protocol Transition:
    • The server sends a ClientboundLoginFinishedPacket to notify the client that the login phase is complete.
    • The client acknowledges this by responding with ServerboundLoginAcknowledgedPacket.
    • The server switches the connection to the Configuration protocol and creates a ServerConfigurationPacketListenerImpl.

3. Configuration Phase

The server configuration phase is a task-based queue that sets up registry entries, client/server resource settings, and initial spawn calculations before the player actually joins the world.

sequenceDiagram
    autonumber
    participant Client
    participant Server (Config)

    Note over Server (Config): startConfiguration()
    Server (Config)-->>Client: Brand / Server Links / Update Features Packets
    Server (Config)-->>Client: Registry/Pack select request (SynchronizeRegistriesTask)
    Client->>Server (Config): ServerboundSelectKnownPacks
    Note over Server (Config): PrepareSpawnTask (Loads player data & chunks)
    Note over Server (Config): JoinWorldTask (Sends finish config packet)
    Server (Config)-->>Client: ClientboundFinishConfigurationPacket
    Client->>Server (Config): ServerboundFinishConfigurationPacket
    Note over Server (Config): Transition connection to Play state

Configuration Task Queue:

  • Server Identity: The server sends initial information like brand name (ClientboundCustomPayloadPacket with BrandPayload) and links (ClientboundServerLinksPacket).
  • SynchronizeRegistriesTask: Sends the server's known resource packs and waits for the client to acknowledge with ServerboundSelectKnownPacks. The server then replies with ClientboundRegistryDataPacket and ClientboundUpdateTagsPacket.
  • Optional Tasks:
    • ServerCodeOfConductConfigurationTask: Prompts client to accept terms.
    • ServerResourcePackConfigurationTask: Sends resource pack download prompts.
  • PrepareSpawnTask:
    1. Loads player data (position, rotation, dimension).
    2. Asynchronously requests spawn chunk loading (radius of 3 chunks around player spawn position).
    3. Holds configuration tick execution until the client's immediate spawn area is fully loaded and ready.
  • JoinWorldTask: Sends ClientboundFinishConfigurationPacket to trigger play state transition.
  • Transition:
    • The client responds with ServerboundFinishConfigurationPacket.
    • The server updates the network handler to the Play protocol template (GameProtocols.CLIENTBOUND_TEMPLATE.bind(...)).
    • The server spawns the player into the level and hands control to ServerGamePacketListenerImpl.

4. Play State Transition (Initial World Sync)

When the connection transitions to the Play state, PlayerList.placeNewPlayer() sends a dense stream of packets to synchronize the player's HUD, environment, inventory, and initial chunks.

       [ Client ]                                     [ Server (Play) ]
           |                                                  |
           | <----------- ClientboundLoginPacket -------------| (EID, difficulty, dimensions...)
           | <------- ClientboundChangeDifficultyPacket ------| (Current difficulty state)
           | <------ ClientboundPlayerAbilitiesPacket -------| (Flying/creative capabilities)
           | <-------- ClientboundSetHeldSlotPacket ----------| (Active hotbar slot index)
           | <------- ClientboundUpdateRecipesPacket ---------| (Synchronize recipes)
           | <--------- Send Commands Tree Packet ------------| (Command syntax helper)
           | <------ ClientboundPlayerInfoUpdatePacket -------| (Initialize online tab list)
           | <--------- Teleport Packet to Spawn -------------| (Pos/Rot snap location)
           |                                                  |
           |                   (Send World Info)              |
           | <----- ClientboundInitializeBorderPacket --------| (World border settings)
           | <------------- Synchronize Clock Packet ---------| (World time / day-night tick)
           | <---- ClientboundSetDefaultSpawnPositionPacket --| (World spawn coordinate)
           | <--------- ClientboundGameEventPacket -----------| (Weather updates: Rain/Thunder)
           | <-------- LEVEL_CHUNKS_LOAD_START Event ---------| (Trigger client chunk loading)
           |                                                  |
           |              (Active Entities & Chunks)          |
           | <------- ClientboundUpdateMobEffectPacket -------| (Apply ongoing status effects)
           | <--------- Initialize Inventory Packet ----------| (Fill inventory UI slots)
  1. ClientboundLoginPacket: Sets up core game parameters (Entity ID, hardcore mode, view distance, simulation distance).
  2. ClientboundChangeDifficultyPacket & ClientboundPlayerAbilitiesPacket: Syncs world difficulty and character flight/speed settings.
  3. ClientboundSetHeldSlotPacket: Syncs the player's currently selected hotbar slot.
  4. ClientboundUpdateRecipesPacket: Syncs stonecutter and generic crafting recipes.
  5. Commands & Permissions: Sends operator status and command syntax mappings.
  6. Scoreboard & Teams: Sends objective lists and color styling data (updateEntireScoreboard).
  7. Player List Info: Sends ClientboundPlayerInfoUpdatePacket to add the joining player and all current players to the tab list.
  8. Position Teleport: Teleports the player's local camera to the spawn position.
  9. Environment/Weather: Syncs the world border, time of day, default spawn points, and weather conditions (e.g., START_RAINING, RAIN_LEVEL_CHANGE).
  10. Start Level Sync: Sends a game event of type LEVEL_CHUNKS_LOAD_START to indicate to the client that it should prepare to receive chunk data.

5. Live World Synchronization Mechanisms

Once a player is in the world, the server continuously updates the player's client via three loops: Chunk Sync, Entity Sync, and World State Sync.

sequenceDiagram
    autonumber
    participant Client
    participant Server (Play)

    Note over Server (Play): PlayerList.placeNewPlayer()
    Server (Play)-->>Client: ClientboundLoginPacket (Entity ID, Dimension keys, View distance...)
    Server (Play)-->>Client: ChangeDifficulty / PlayerAbilities / SetHeldSlot / UpdateRecipes Packets
    Server (Play)-->>Client: Teleport (initial position)
    Server (Play)-->>Client: InitializeBorder / Sync Time / DefaultSpawnPosition Packets
    Server (Play)-->>Client: LEVEL_CHUNKS_LOAD_START Game Event
    
    rect rgb(200, 220, 240)
        Note over Server (Play), Client: Chunk Synchronization Loop
        Server (Play)-->>Client: ClientboundChunkBatchStartPacket
        Server (Play)-->>Client: ClientboundLevelChunkWithLightPacket (multiple chunks)
        Server (Play)-->>Client: ClientboundChunkBatchFinishedPacket
    end

    rect rgb(220, 240, 200)
        Note over Server (Play), Client: Entity Tracking Loop
        Server (Play)-->>Client: ClientboundBundlePacket (Spawn Entity, Metadata, Attributes, Equipment)
        Server (Play)-->>Client: ClientboundMoveEntityPacket / EntityPositionSyncPacket / RotateHeadPacket
        Server (Play)-->>Client: ClientboundRemoveEntitiesPacket (when entity out of range)
    end

    rect rgb(240, 200, 220)
        Note over Server (Play), Client: Dynamic World State Loop
        Server (Play)-->>Client: ClientboundBlockUpdatePacket (single block)
        Server (Play)-->>Client: ClientboundSectionBlocksUpdatePacket (multiple blocks in section)
        Server (Play)-->>Client: ClientboundLightUpdatePacket (lighting recalculations)
    end

A. Chunk Synchronization

Minecraft manages which chunks are loaded on the client through the player's view distance and ChunkTrackingView.

  • Movement Tracking: As a player walks, the difference between their previous ChunkTrackingView and current ChunkTrackingView is computed:
    • New Chunks: Scheduled for sending via markChunkPendingToSend(player, chunk).
    • Old Chunks: Cleared from the client via ClientboundForgetLevelChunkPacket.
  • Chunk Batching: To prevent network congestion, the server uses PlayerChunkSender to batch chunks:
    1. Sends a ClientboundChunkBatchStartPacket.
    2. Sends individual chunk data using ClientboundLevelChunkWithLightPacket (containing blocks, state mappings, tile entities, and light values).
    3. Sends ClientboundChunkBatchFinishedPacket confirming the batch size.
    4. Waits for the client to acknowledge before sending the next batch (the batch rate dynamically throttles based on the client's processing feedback).

B. Entity Tracking & Synchronization

The server tracks close entities (players, items, projectiles, mobs) on a per-player basis using ChunkMap.TrackedEntity.

1. Spawn Sync (Adding a Pairing)

When an entity enters a player's tracking range:

  1. The server calls addPairing(player), collecting all initialization packets.
  2. It packs them inside a ClientboundBundlePacket to guarantee atomic client rendering:
    • getAddEntityPacket(...): Spawns the visual representation of the entity.
    • ClientboundSetEntityDataPacket: Syncs metadata values (e.g., if a creeper is ignited, if a wolf is sitting).
    • ClientboundUpdateAttributesPacket: Syncs movement speed, health limits, etc.
    • ClientboundSetEquipmentPacket: Syncs armor, shield, and hand items.
    • ClientboundSetPassengersPacket: Syncs riding links.

2. Position & State Sync (Incremental Updates)

Every tick, the server runs sendChanges() inside ServerEntity:

  • Relative Movement: If the movement delta since the last packet is small, the server encodes it using a VecDeltaCodec (fitting into a short representation) and sends:
    • ClientboundMoveEntityPacket.Pos (position only)
    • ClientboundMoveEntityPacket.Rot (rotation only)
    • ClientboundMoveEntityPacket.PosRot (both position and rotation)
  • Hard Synced Teleportation: If the displacement exceeds the short delta limit, the riding/grounded state changes, or 400 ticks (FORCED_TELEPORT_PERIOD) have passed, the server sends a ClientboundEntityPositionSyncPacket to force-snap the position.
  • Head Rotation: Head yaw changes are tracked separately and sent via ClientboundRotateHeadPacket.
  • Velocity: Real-time motion forces (like knockback) are sent using ClientboundSetEntityMotionPacket.

3. Despawn Sync (Removing a Pairing)

When an entity is destroyed or moves out of the player's tracking range, removePairing(player) is executed, sending a ClientboundRemoveEntitiesPacket to free memory on the client.


C. Dynamic World State Updates

Real-time edits to the environment (player building, chest placements, water flows) are pushed block-by-block using ChunkHolder:

  • Block Change Recording: When a block updates, blockChanged(pos) records the position relative to its 16x16x16 section.
  • Broadcasting Updates:
    • Single Block Change: The server sends a ClientboundBlockUpdatePacket containing the coordinate and the new block state.
    • Multiple Block Changes: If multiple blocks update in the same section within a single tick, they are consolidated and sent as a ClientboundSectionBlocksUpdatePacket to save bandwidth.
    • Tile Entities: If the updated block holds a block entity (like a sign, container, or banner), the server fetches and broadcasts its NBT tag via ClientboundBlockEntityDataPacket.
    • Light Recalculation: If block updates affect ambient brightness, a ClientboundLightUpdatePacket is broadcasted.

Packet sniffer proxy (development)

MITM proxy: the Java client connects to a local minecraft-protocol server; the sniffer opens a second authenticated client to config.server and relays decrypted packets both ways while logging to JSONL.

npm run sniffer
  • Listens on config.sniffer.port (default 25567); upstream target is config.server.
  • Connect the Java client to the sniffer (not 25566). One client at a time.
  • Logs: logs/sniffer/session-<timestamp>.jsonl with "type":"packet" entries (dir, state, name, payload or summary).
  • sniffer.upstreamAuth: "microsoft" (default) or "offline" for the upstream leg.
  • sniffer.onlineMode: false (default) lets the Java client join the sniffer without Mojang checking the sniffer itself; upstream still uses upstreamAuth.
  • Server list ping (nextState: 1) uses a raw TCP pass-through; Join (nextState: 2) runs the MITM path (login, registry_data, map_chunk, …).
  • registry_data / chunk packets are relayed with writeRaw where needed so NBT stays byte-identical.

For the main FlayerProxy handoff path (25566), captured upstream config is still replayed with writeRaw so registry NBT stays byte-identical.