Skip to content

Writing a Client

Koding edited this page Aug 24, 2020 · 1 revision

🤝 Writing a Client

The protocol module is designed to be a low-level implementation, leaving much of the logic to the developer. By default, CraftLib will not automatically respond to or process any packets from the server.

Please note that CraftLib currently lacks an authentication module. This is planned, however at this time you will have to fetch an access token yourself.

1. Building a Connection

The first step of connecting to a Minecraft server is using the connection builder. This is a suspend function which defines the initial packet handler and establishes the connection with the remote host.

The below code will connect to a Minecraft server running locally on port 25565 and log any packets the server sent. However, since there is no packet handling defined, no packets will be logged.

suspend fun main() {
    // This will connect to a Minecraft server running on
    // localhost with the port 25565.
    MinecraftProtocol.connect(InetAddress.getByName("localhost"), 25565) {
        // When true, this will install an additional handler in the connection to
        // print additional information about packets being sent/received
        // In a production environment this should usually be set to false.
        debug = true

        // If this is greater than 0, an additional handler
        // will be added to close the connection if no packets
        // have been read/written in the duration below (30s)
        timeout = 30

        // When true, the connect function will block until
        // the connection has fully terminated.
        connectSync = true

        // Sets the TCP No Delay value to true if this is defined.
        noDelay = true

        // This sets the initial packet handler which should process
        // the handshake packets. You will learn
        // about this in the next step.
        handler = CustomPacketHandler
    }
}

2. Defining a minimal packet handler

To actually communicate with a Minecraft server, you will need to define a custom packet handler. This handler controls what your client does after it has been connected, including catching exceptions, sending initial packets and reading from the server.

The example below will execute the most basic login flow in offline (cracked) mode, switching into the play state and logging all packets.

object CustomPacketHandler : PacketHandler {
    override fun connected(connection: NettyConnection) {
        // Configure packets to use the Minecraft 1.8 format
        connection.version = ProtocolVersion.MC1_8

        // The initial state should be set to handshake so that we
        // can send the ClientHandshakePacket and have it encode properly
        connection.state = MinecraftProtocol.HANDSHAKE

        // Send the handshake packet, providing the address as localhost,
        // port as 25565 and setting our next target state as LOGIN.
        // This allows us to send the login start packet and begin authenticating
        connection.send(
            ClientHandshakePacket(
                ProtocolVersion.MC1_8,
                "localhost",
                25565,
                MinecraftProtocol.LOGIN
            )
        ) {
            // Change the state to login after this packet has been sent
            // so we can send the LoginStartPacket
            connection.state = MinecraftProtocol.LOGIN

            // Send the login start packet with our desired username
            // which the server should use when in game.
            connection.send(ClientLoginStartPacket("Example"))
        }
    }

    override fun received(connection: NettyConnection, packet: Packet) {
        when (packet) {
            // After the login success packet has been read, we know that
            // the client has been successfully authenticated with the server and
            // should begin reading PLAY packets.
            is ServerLoginSuccessPacket -> connection.state = MinecraftProtocol.PLAY
        }
    }

    override fun exception(connection: NettyConnection, cause: Throwable) {
        // For any exceptions that we encounter, log them to
        // the console so they can be debugged
        cause.printStackTrace()
    }
}

Login Flow

The following flow assumes that C represents the client and S represents the server. Therefore, C->S means a packet has been sent from the client to the server.

  • [C->S] Handshake The client sends a handshake packet to the server, switching the state to login.
  • [C->S] Login Start The client sends a login start packet with the desired username, telling the server it is ready to login.
  • Online mode authentication (Optional)
    • [S->C] Encryption Request The server requests an generated public key, secret key and verify token from the client. It is expected that the client sends a request to Mojang with their access token and a generated Server ID hash. After sending this packet, encryption should be enabled using the generated secret key.
    • [C->S] Encryption Response The client sends an encryption response packet to the server with the public key, secret and verify token.
  • [S->C] Login Success The server will respond with a login success packet if the authentication was successful and immediately switch to the PLAY state. The client should also switch into PLAY.

3. More Coming Soon!

This wiki will be updated with further information soon. Keep an eye out! 👀