Skip to content

Multiplayer utilities

The following components are used for various multiplayer interacions (generally needed during the server joining process).

mcproto.multiplayer.JoinAcknowledgeData

Bases: TypedDict

Response from :func:join_check (hasJoined minecraft API endpoint).

This response contains information on the user has submitted the :func:join_request. (uuid, name, and player skin properties)

mcproto.multiplayer.JoinAcknowledgeProperty

Bases: TypedDict

Skin blob data from :class:JoinAcknowledgeData.

mcproto.multiplayer.UserJoinCheckFailedError

Bases: Exception

Exception raised when :func:join_check fails.

This signifies that the Minecraft session API server didn't contain a join request for the server_hash and client_username, and it therefore didn't acknowledge the join.

This means the user didn't confirm this join with Minecraft API (didn't call :func:join_request), hence the validity of this account can't be verified. The server should kick the user and end the join flow.

mcproto.multiplayer.UserJoinRequestErrorKind

Bases: str, Enum

Enum for various different kinds of exceptions that can occur during :func:join_request.

mcproto.multiplayer.UserJoinRequestErrorKind.from_status_error classmethod

from_status_error(code: int, err_msg: str | None) -> UserJoinRequestErrorKind

Determine the error kind based on the status code and error message.

mcproto.multiplayer.UserJoinRequestFailedError

Bases: Exception

Exception raised when :func:join_request fails.

This can be caused by various reasons. See: :class:UserJoinRequestErrorKind enum class. The most likely case for this error is invalid authentication token, or the user being banned from multiplayer.

mcproto.multiplayer.UserJoinRequestFailedError.msg property

msg: str

Produce a message for this error.

mcproto.multiplayer.compute_server_hash

compute_server_hash(server_id: str, shared_secret: bytes, server_public_key: RSAPublicKey) -> str

Compute a hash to be sent as 'serverId' field to Mojang session server.

This function is used for :func:join_request and :func:join_check functions, which require this hash value.

This SHA1 hash is computed based on the server_id, server_public_key and shared_secret. Together, these values ensure that there can't be any middle-man listening in after encryption is established.

This is because a middle man/proxy who would want to listed into the encrypted communication would need to know the encryption key (shared_secret). A proxy can capture this key, as the client sends it over to the server in :class:~mcproto.packets.login.login.LoginEncryptionResponse packet, however it is sent encrypted. The client performs this encryption with a public key, which it got from the server, in :class:mcproto.packets.login.login.LoginEncryptionRequest packet.

That mans that for a proxy to be able to actually obtain this shared secret value, it would need to be able to capture the encryption response, and decrypt the shared secret value. That means it would need to send a spoofed version of the encryption request packet, with the server's public key replaced by one that the proxy owns a private key for. This will work, and the proxy could indeed decrypt the sent shared secret now. All it would need to do now is send this shared secret to the server. That's easy, just re-encrypt it with the server's original public key, and send it in a custom encryption request!

So then it seems that it's possible to intercept the client-server communication and spy in, and indeed, this will work with offline mode (warez) servers, however with online mode servers, that's where this function comes in!

Online mode servers rely on an API server from Mojang's (session server), which the client informs of the join. The server then queries this server for an acknowledgement of this join, and only if this session server confirms that the client did indeed inform it of this join will the server allow this client to join.

The trick is, this request to inform the session server of the join can only be performed by the client directly, a proxy can't simulate it, because this request requires a token for the minecraft account, which only the launcher has. The client never sends this token to the server, only to the Mojang's session server, so a proxy wouldn't have it.

This join request then contains those 3 variables, one of which being the public key itself, so if the proxy sent a different key, the server would no longer arrive at the same server hash, and the check would fail, so the server wouldn't allow this client to join.

mcproto.multiplayer.join_check async

join_check(client: AsyncClient, client_username: str, server_hash: str, client_ip: str | None = None) -> JoinAcknowledgeData

Check with the Mojang session server if a join request was made.

This function is called by the server in online mode (non-warez), to verify that the joining client really does have an official minecraft account. The client will first inform the server about this join request (:func:join_request), server then runs this check confirming the client is who they say they are.

This request should be performed after receiving the after receiving the :class:~mcproto.packets.login.login.LoginEncryptionResponse packet.

This request uses a server_hash, this is the value under which the client has submitted their join request, and we'll now be checking for that submission with that same value. This is a hash composed of various values, which together serve as a way to prevent any MITMA (man in the middle attacks). To obtain this hash, see :func:compute_server_hash. This function's docstring also includes description for why and how this prevents a MITMA.

:param client: HTTPX async client to make the HTTP request with. :param client_username: Must match joining the username of the joining client (case sensitive).

Note: This is the in-game nickname of the selected profile, not Mojang account name
(which is never sent to the server). Servers should use the name in "name" field which was
received in the :class:`~mcproto.packets.login.login.LoginStart` packet.

:param server_hash: SHA1 hash of the server (see :func:compute_server_hash) :param client_ip: IP address of the connecting player (optional)

Servers only include this when 'prevent-proxy-connections' is set to true in server.properties

mcproto.multiplayer.join_request async

join_request(client: AsyncClient, account: Account, server_hash: str) -> None

Inform the Mojang session server about this new user join.

This function is called by the client, when joining an online mode (non-warez) server. This is required and the server will check that this request was indeed made (:func:join_check).

This request should be performed after receiving the :class:~mcproto.packets.login.login.LoginEncryptionRequest packet, but before sending the :class:~mcproto.packets.login.login.LoginEncryptionResponse.

Performing this request requires an :class:~mcproto.auth.account.Account instance, as this request is here to ensure that only original Minceraft accounts (officially bought accounts) can join.

This request uses a server_hash to identify which server is the client attempting to join. This hash is composed of various values, which together serve as a way to prevent any MITMA (man in the middle attacks). To obtain this hash, see :func:compute_server_hash. This function's docstring also includes description for why and how this prevents a MITMA.

:param client: HTTPX async client to make the HTTP request with. :param account: Instance of an account containing the minecraft token necessary for this request. :param server_hash: SHA1 hash of the server (see :func:compute_server_hash)