Skip to content

node-minecraft-protocol: brand and Client Information are sent only on the first configuration entry — vanilla re-sends them after every server transfer #44

Description

@u9g

Title: node-minecraft-protocol: brand and Client Information are sent only on the first configuration entry — vanilla re-sends them after every server transfer

Body:

What happens

enterConfigState gates brand + settings behind a one-shot flag:

// Brand then Client Information are sent once per connection, on the first entry
// into configuration; re-entering configuration from play sends nothing else.
if (!sentClientInformation) { ... }

With Velocity modern transfers (play → configuration re-entry → play), every server the client is transferred to sees a client with no brand and no Client Information. The vanilla client sends both on every configuration phase (each world gets them again). Some servers validate the brand per world; without it the client is treated as non-vanilla (we were console-kicked "Cheating" on a network whose game servers check, while its lobbies didn't).

Fix (field-verified)

Send both on every entry into configuration (the original Hypixel ordering constraint is preserved — they still go out immediately on entry):

sentClientInformation = true
client.write('custom_payload', { channel: 'minecraft:brand', data: ... })
client.write('settings', { ... })

Fresh-account survival in a previously-kicking game world went from <60s to indefinite after this plus the pong-order and chunk-stall fixes. Single change alone is necessary but not sufficient; the three together stopped the kicks.

Patch: patches/nmp-brand-settings-every-config.patch (23 lines, src/client/play.js).

Patch

diff --git a/packages/minecraft-protocol/src/client/play.js b/packages/minecraft-protocol/src/client/play.js
index d3e3fd0..9be98ee 100644
--- a/packages/minecraft-protocol/src/client/play.js
+++ b/packages/minecraft-protocol/src/client/play.js
@@ -56,29 +56,28 @@ module.exports = function (client, options) {
         client.write('configuration_acknowledged', {})
       }
       client.state = states.CONFIGURATION
-      // Brand then Client Information are sent once per connection, on the first entry
-      // into configuration; re-entering configuration from play sends nothing else. Some
-      // servers (e.g. Hypixel) close the socket unless Client Information arrives before
-      // they send finish_configuration
-      if (!sentClientInformation) {
-        sentClientInformation = true
-        client.write('custom_payload', {
-          channel: 'minecraft:brand',
-          data: client.serializer.proto.createPacketBuffer('string', options.brand ?? 'vanilla')
-        })
-        const clientSettings = options.clientSettings || {}
-        client.write('settings', {
-          locale: clientSettings.locale ?? 'en_us',
-          viewDistance: clientSettings.viewDistance ?? 12,
-          chatFlags: clientSettings.chatFlags ?? 0,
-          chatColors: clientSettings.chatColors ?? true,
-          skinParts: clientSettings.skinParts ?? 127,
-          mainHand: clientSettings.mainHand ?? 1,
-          enableTextFiltering: clientSettings.enableTextFiltering ?? false,
-          enableServerListing: clientSettings.enableServerListing ?? true,
-          particleStatus: clientSettings.particleStatus ?? 'all'
-        })
-      }
+      // Brand then Client Information go out on EVERY entry into configuration, including
+      // re-entry after a server transfer: the vanilla client re-sends both for each world,
+      // and servers that validate the brand per world never see it again otherwise.
+      // Hypixel closes the socket unless Client Information arrives before they send
+      // finish_configuration, hence the send right here rather than later.
+      sentClientInformation = true
+      client.write('custom_payload', {
+        channel: 'minecraft:brand',
+        data: client.serializer.proto.createPacketBuffer('string', options.brand ?? 'vanilla')
+      })
+      const clientSettings = options.clientSettings || {}
+      client.write('settings', {
+        locale: clientSettings.locale ?? 'en_us',
+        viewDistance: clientSettings.viewDistance ?? 12,
+        chatFlags: clientSettings.chatFlags ?? 0,
+        chatColors: clientSettings.chatColors ?? true,
+        skinParts: clientSettings.skinParts ?? 127,
+        mainHand: clientSettings.mainHand ?? 1,
+        enableTextFiltering: clientSettings.enableTextFiltering ?? false,
+        enableServerListing: clientSettings.enableServerListing ?? true,
+        particleStatus: clientSettings.particleStatus ?? 'all'
+      })
       // The server omits the registry entries of every pack in the reply, so a pack may
       // only be listed when its data is available locally
       client.once('select_known_packs', (packet) => {

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions