From 448b19abbac3084eade27620f7de1e877562ad7f Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 15:56:50 -0400 Subject: [PATCH 1/5] Answer cookie requests from stored cookies like the vanilla client A server or proxy that sends cookie_request (1.20.5+) in the login, configuration or play state never got a reply, so the connection hung. Keep a per-connection cookie map on client._cookies, fill it from store_cookie packets and from the new `cookies` option (for carrying cookies over a transfer), and answer every cookie_request with the stored value or an absent one, as vanilla does. --- docs/API.md | 5 +++ src/client/cookies.js | 29 +++++++++++++++ src/createClient.js | 2 ++ test/serverTest.js | 84 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 src/client/cookies.js diff --git a/docs/API.md b/docs/API.md index 1cd65246..7d93e56b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -147,6 +147,7 @@ Returns a `Client` instance and perform login. * id : a numeric client id used for referring to multiple clients in a server * validateChannelProtocol (optional) : whether or not to enable protocol validation for custom protocols using plugin channels. Defaults to true * disableChatSigning (optional) : Don't try obtaining chat signing keys from Mojang (1.19+) + * cookies (optional) : cookies to answer `cookie_request` packets with, as an object or Map of key to Buffer (1.20.5+). Pass the previous connection's `client._cookies` when following a `transfer` packet, like the vanilla client does * clientSettings (optional) : Client Information (settings) sent to the server during the configuration phase (1.20.2+). All fields are optional and default to vanilla-safe values: * locale : language/locale string, default `'en_us'` * viewDistance : view distance in chunks, default `10` @@ -168,6 +169,10 @@ Returns a `Client` instance and perform login. Create a new client, if `isServer` is true then it is a server-side client, otherwise it's a client-side client. Takes a minecraft `version` as second argument. +### client._cookies + +Map of cookies (key to Buffer) received through `store_cookie` packets, seeded from the `cookies` option (1.20.5+). The client answers `cookie_request` packets from it in the login, configuration and play states, replying with an absent value for unknown cookies, like the vanilla client. + ### client.write(name, params) write a packet diff --git a/src/client/cookies.js b/src/client/cookies.js new file mode 100644 index 00000000..16509df4 --- /dev/null +++ b/src/client/cookies.js @@ -0,0 +1,29 @@ +'use strict' + +// Cookies (1.20.5+) are keyed by resource location and answered from one map in every +// state, like the vanilla client's serverCookies; `cookie_request` and `cookie_response` +// only exist in the login, configuration and play states of versions that have cookies, +// so the listeners never fire (nor write) elsewhere. +module.exports = function (client, options) { + client._cookies = new Map(options.cookies instanceof Map ? options.cookies : Object.entries(options.cookies ?? {})) + + client.on('store_cookie', (packet) => { + client._cookies.set(packet.key, packet.value) + }) + + client.on('cookie_request', (packet) => { + let value = client._cookies.get(packet.cookie) + if (value === undefined && !cookieValueIsOptional(client.version)) { + // An absent option and an empty ByteArray both serialize as a single 0x00 byte, so + // this reply is wire-identical to vanilla's null where minecraft-data (1.21.8) + // declares the value without the option wrapper + value = Buffer.alloc(0) + } + client.write('cookie_response', { key: packet.cookie, value }) + }) +} + +function cookieValueIsOptional (version) { + const type = require('minecraft-data')(version).protocol.types.packet_common_cookie_response + return type[1].find(field => field.name === 'value').type[0] === 'option' +} diff --git a/src/createClient.js b/src/createClient.js index 432c8fec..5fbe9632 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -10,6 +10,7 @@ const auth = require('./client/mojangAuth') const microsoftAuth = require('./client/microsoftAuth') const setProtocol = require('./client/setProtocol') const play = require('./client/play') +const cookies = require('./client/cookies') const tcpDns = require('./client/tcp_dns') const autoVersion = require('./client/autoVersion') const pluginChannels = require('./client/pluginChannels') @@ -71,6 +72,7 @@ function createClient (options) { keepalive(client, options) encrypt(client, options) play(client, options) + cookies(client, options) compress(client, options) pluginChannels(client, options) versionChecking(client, options) diff --git a/test/serverTest.js b/test/serverTest.js index 95cb3382..d233bd69 100644 --- a/test/serverTest.js +++ b/test/serverTest.js @@ -29,6 +29,10 @@ for (const supportedVersion of mc.supportedVersions) { let PORT const mcData = require('minecraft-data')(supportedVersion) const version = mcData.version + // minecraft-data 1.21.8 declares cookie_response.value as a non-optional ByteArray, so a + // stored cookie cannot be echoed there + const hasCookies = 'packet_common_cookie_request' in mcData.protocol.types && + mcData.protocol.types.packet_common_cookie_response[1][1].type[0] === 'option' const loginPacket = (client, server) => { if (mcData.loginPacket) { @@ -564,6 +568,86 @@ for (const supportedVersion of mc.supportedVersions) { }) }) }) + + if (hasCookies) { + it('answers login cookie requests from the cookies option', function (done) { + const seeded = Buffer.from('seeded-cookie') + // A vanilla server only enables compression once the login cookie exchange is + // over, so drive the login state by hand instead of through mc.createServer + const server = net.createServer((socket) => { + const client = new mc.Client(true, version.minecraftVersion) + client.setSocket(socket) + client.on('set_protocol', () => { + client.state = mc.states.LOGIN + }) + client.on('login_start', () => { + client.write('cookie_request', { cookie: 'test:seeded' }) + }) + client.on('cookie_response', (packet) => { + assert.strictEqual(client.state, mc.states.LOGIN) + assert.deepStrictEqual(packet, { key: 'test:seeded', value: seeded }) + client.end('done') + server.close() + }) + }) + server.on('close', done) + server.listen(PORT, '127.0.0.1', () => { + mc.createClient({ + username: 'cookieMonster', + host: '127.0.0.1', + version: version.minecraftVersion, + port: PORT, + cookies: { 'test:seeded': seeded } + }) + }) + }) + + it('answers cookie requests in configuration and play', function (done) { + const seeded = Buffer.from('seeded-cookie') + const stored = Buffer.from('stored-cookie') + const server = mc.createServer({ + 'online-mode': false, + version: version.minecraftVersion, + port: PORT + }) + const responses = [] + server.on('connection', function (client) { + client.on('cookie_response', (packet) => { + responses.push({ state: client.state, key: packet.key, value: packet.value }) + if (responses.length === 3) { + assert.deepStrictEqual(responses, [ + { state: mc.states.CONFIGURATION, key: 'test:seeded', value: seeded }, + { state: mc.states.CONFIGURATION, key: 'test:stored', value: stored }, + { state: mc.states.PLAY, key: 'test:unknown', value: undefined } + ]) + server.close() + } + }) + // Runs before the login plugin's own login_acknowledged handler, which sends + // registry data and finish_configuration + client.once('login_acknowledged', () => { + client.state = mc.states.CONFIGURATION + client.write('cookie_request', { cookie: 'test:seeded' }) + client.write('store_cookie', { key: 'test:stored', value: stored }) + client.write('cookie_request', { cookie: 'test:stored' }) + }) + }) + server.on('playerJoin', function (client) { + client.write('login', loginPacket(client, server)) + client.write('cookie_request', { cookie: 'test:unknown' }) + }) + server.on('close', done) + server.on('listening', function () { + mc.createClient({ + username: 'cookieMonster', + host: '127.0.0.1', + version: version.minecraftVersion, + port: PORT, + cookies: { 'test:seeded': seeded } + }) + }) + }) + } }) } From 856989aad0fc4a3159305df2d7031d7ad22d636a Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 15:57:52 -0400 Subject: [PATCH 2/5] Send brand then Client Information once on entering configuration like vanilla The vanilla client answers login_acknowledged with a minecraft:brand custom_payload followed by Client Information, and sends neither again when a server moves it back to configuration from play: it only replies with configuration_acknowledged. nmp sent no brand, re-sent the settings on every configuration entry and defaulted the view distance to 10 where vanilla uses 12. Send the brand (`brand` option, default 'vanilla') and the settings on the first entry only, with the vanilla defaults. --- docs/API.md | 5 ++-- src/client/play.js | 47 ++++++++++++++++++++--------------- test/serverTest.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 21 deletions(-) diff --git a/docs/API.md b/docs/API.md index 7d93e56b..9420d92f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -148,9 +148,10 @@ Returns a `Client` instance and perform login. * validateChannelProtocol (optional) : whether or not to enable protocol validation for custom protocols using plugin channels. Defaults to true * disableChatSigning (optional) : Don't try obtaining chat signing keys from Mojang (1.19+) * cookies (optional) : cookies to answer `cookie_request` packets with, as an object or Map of key to Buffer (1.20.5+). Pass the previous connection's `client._cookies` when following a `transfer` packet, like the vanilla client does - * clientSettings (optional) : Client Information (settings) sent to the server during the configuration phase (1.20.2+). All fields are optional and default to vanilla-safe values: + * brand (optional) : client brand sent on the `minecraft:brand` plugin channel when first entering the configuration phase (1.20.2+), default `'vanilla'` + * clientSettings (optional) : Client Information (settings) sent to the server, after the brand, when first entering the configuration phase (1.20.2+); like the vanilla client, they are not re-sent when a server sends the client back to configuration. All fields are optional and default to the vanilla values: * locale : language/locale string, default `'en_us'` - * viewDistance : view distance in chunks, default `10` + * viewDistance : view distance in chunks, default `12` * chatFlags : chat mode, `0` = enabled, `1` = commands only, `2` = hidden, default `0` * chatColors : whether chat colors are enabled, default `true` * skinParts : displayed skin parts bitmask, default `127` diff --git a/src/client/play.js b/src/client/play.js index 221f14c4..28a78ea7 100644 --- a/src/client/play.js +++ b/src/client/play.js @@ -35,6 +35,7 @@ module.exports = function (client, options) { const mcData = require('minecraft-data')(client.version) client.uuid = packet.uuid client.username = packet.username + let sentClientInformation = false if (mcData.supportFeature('hasConfigurationState')) { client.write('login_acknowledged', {}) @@ -53,25 +54,33 @@ module.exports = function (client, options) { client.write('configuration_acknowledged', {}) } client.state = states.CONFIGURATION - // Mirror the vanilla client, which sends Client Information during the - // configuration phase. Some servers (e.g. Hypixel) wait for it before - // sending finish_configuration and will close the socket otherwise. - // Defaults are vanilla-safe and can be overridden per-field via the - // `clientSettings` option. A client that also sends Client Information in - // the play state (e.g. mineflayer on its 'login' event) still takes - // precedence there, exactly as the vanilla client re-sends settings. (#3623) - const clientSettings = options.clientSettings || {} - client.write('settings', { - locale: clientSettings.locale ?? 'en_us', - viewDistance: clientSettings.viewDistance ?? 10, - 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 vanilla client sends its brand and then Client Information right after + // login_acknowledged, and only those once: re-entering configuration from play + // is acknowledged with nothing else. Some servers (e.g. Hypixel) wait for the + // Client Information before sending finish_configuration and close the socket + // otherwise. Defaults are the vanilla ones and can be overridden per-field via + // the `clientSettings` option; a client that also sends Client Information in + // the play state (e.g. mineflayer on its 'login' event) still takes precedence + // there, exactly as the vanilla client re-sends settings. (#3623) + 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' + }) + } client.once('select_known_packs', () => { client.write('select_known_packs', { packs: [] }) }) diff --git a/test/serverTest.js b/test/serverTest.js index d233bd69..23fa4990 100644 --- a/test/serverTest.js +++ b/test/serverTest.js @@ -569,6 +569,67 @@ for (const supportedVersion of mc.supportedVersions) { }) }) + if (mcData.supportFeature('hasConfigurationState')) { + it('sends brand then client information once when entering configuration', function (done) { + const server = mc.createServer({ + 'online-mode': false, + version: version.minecraftVersion, + port: PORT + }) + const received = [] + server.on('connection', function (client) { + client.on('custom_payload', (packet) => { + if (client.state !== mc.states.CONFIGURATION) return + received.push({ channel: packet.channel, brand: packet.data.subarray(1).toString('utf8'), lengthPrefix: packet.data[0] }) + }) + client.on('settings', (packet) => { + if (client.state !== mc.states.CONFIGURATION) return + received.push({ settings: packet }) + }) + // The nmp server does not implement re-configuration itself + client.on('configuration_acknowledged', () => { + client.state = mc.states.CONFIGURATION + client.once('finish_configuration', () => { + client.state = mc.states.PLAY + assert.deepStrictEqual(received, [ + { channel: 'minecraft:brand', brand: 'nmp-test', lengthPrefix: 8 }, + { + settings: { + locale: 'en_us', + viewDistance: 7, + chatFlags: 0, + chatColors: true, + skinParts: 127, + mainHand: 1, + enableTextFiltering: false, + enableServerListing: true, + ...(mcData.version.version >= 768 ? { particleStatus: 'all' } : {}) + } + } + ]) + server.close() + }) + client.write('finish_configuration', {}) + }) + }) + server.on('playerJoin', function (client) { + client.write('login', loginPacket(client, server)) + client.write('start_configuration', {}) + }) + server.on('close', done) + server.on('listening', function () { + mc.createClient({ + username: 'configPlayer', + host: '127.0.0.1', + version: version.minecraftVersion, + port: PORT, + brand: 'nmp-test', + clientSettings: { viewDistance: 7 } + }) + }) + }) + } + if (hasCookies) { it('answers login cookie requests from the cookies option', function (done) { const seeded = Buffer.from('seeded-cookie') From 9a4f3ccf209a81eab5c15297f40377adabf9bd8f Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 15:58:04 -0400 Subject: [PATCH 3/5] Start a new chat session on every login packet like the vanilla client The chat session was only created for the first login packet. After a server switch through re-configuration the next login packet left the old session in place, while the vanilla client discards its session on every login and announces a new one (fresh session UUID, index 0) with the same profile key pair. Do the same. --- src/client/play.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/play.js b/src/client/play.js index 28a78ea7..a92bae9a 100644 --- a/src/client/play.js +++ b/src/client/play.js @@ -11,7 +11,9 @@ module.exports = function (client, options) { } }) - client.once('login', (packet) => { + // Every login packet starts a fresh chat session (new session UUID, message index 0) + // with the same profile key pair, as the vanilla client does after a reconfiguration + client.on('login', (packet) => { if (packet.enforcesSecureChat) client.serverFeatures.enforcesSecureChat = packet.enforcesSecureChat const mcData = require('minecraft-data')(client.version) if (mcData.supportFeature('useChatSessions') && client.profileKeys && client.cipher && client.session.selectedProfile.id === client.uuid.replace(/-/g, '')) { From 1e49f4591fd7aeba02c07724dd25c8f763ca9e27 Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 16:00:28 -0400 Subject: [PATCH 4/5] Select known packs shared with the server like the vanilla client The vanilla client answers select_known_packs with the packs from the server's list that it has locally (minecraft:core at its own version on a vanilla server), and the server then omits the registry entries of those packs. nmp always replied with no packs. Add a `knownPacks` option listing the client's local packs and reply with their intersection with the server's list. It defaults to [] so the server keeps sending its full registry data, which consumers such as mineflayer depend on. --- docs/API.md | 1 + src/client/play.js | 10 ++++++++-- test/serverTest.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 9420d92f..565bdee4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -159,6 +159,7 @@ Returns a `Client` instance and perform login. * enableTextFiltering : default `false` * enableServerListing : whether the player appears in server status player samples, default `true` * particleStatus : `'all'`, `'decreased'` or `'minimal'` (1.21.3+), default `'all'` + * knownPacks (optional) : data packs the client has locally, as an array of `{ namespace, id, version }` (1.20.5+). The reply to the server's `select_known_packs` is the part of the server's list found here, like the vanilla client, which knows `{ namespace: 'minecraft', id: 'core', version: '' }`. The server omits the registry entries of the packs in the reply, so the default is `[]` and the server sends its full registry data * realms : An object which should contain one of the following properties: `realmId` or `pickRealm`. When defined will attempt to join a Realm without needing to specify host/port. **The authenticated account must either own the Realm or have been invited to it** * realmId : The id of the Realm to join. * pickRealm(realms) : A function which will have an array of the user Realms (joined/owned) passed to it. The function should return a Realm. diff --git a/src/client/play.js b/src/client/play.js index a92bae9a..c3c6be84 100644 --- a/src/client/play.js +++ b/src/client/play.js @@ -83,8 +83,14 @@ module.exports = function (client, options) { particleStatus: clientSettings.particleStatus ?? 'all' }) } - client.once('select_known_packs', () => { - client.write('select_known_packs', { packs: [] }) + // Vanilla answers with the server's packs it has locally; a pack in the reply makes + // the server skip that pack's registry entries, so nothing is known by default + client.once('select_known_packs', (packet) => { + const knownPacks = options.knownPacks ?? [] + client.write('select_known_packs', { + packs: packet.packs.filter(pack => knownPacks.some(known => + known.namespace === pack.namespace && known.id === pack.id && known.version === pack.version)) + }) }) client.once('code_of_conduct', () => { client.write('accept_code_of_conduct', {}) diff --git a/test/serverTest.js b/test/serverTest.js index 23fa4990..9d6d1b2e 100644 --- a/test/serverTest.js +++ b/test/serverTest.js @@ -630,6 +630,42 @@ for (const supportedVersion of mc.supportedVersions) { }) } + if ('packet_common_select_known_packs' in mcData.protocol.types) { + it('selects the known packs it shares with the server', function (done) { + const core = { namespace: 'minecraft', id: 'core', version: version.minecraftVersion } + const server = mc.createServer({ + 'online-mode': false, + version: version.minecraftVersion, + port: PORT + }) + let reply + server.on('connection', function (client) { + client.on('select_known_packs', (packet) => { + reply = packet.packs + }) + // Runs before the login plugin's own login_acknowledged handler + client.once('login_acknowledged', () => { + client.state = mc.states.CONFIGURATION + client.write('select_known_packs', { packs: [core, { namespace: 'test', id: 'server-only', version: '1' }] }) + }) + }) + server.on('playerJoin', function () { + assert.deepStrictEqual(reply, [core]) + server.close() + }) + server.on('close', done) + server.on('listening', function () { + mc.createClient({ + username: 'packPlayer', + host: '127.0.0.1', + version: version.minecraftVersion, + port: PORT, + knownPacks: [core, { namespace: 'test', id: 'client-only', version: '1' }] + }) + }) + }) + } + if (hasCookies) { it('answers login cookie requests from the cookies option', function (done) { const seeded = Buffer.from('seeded-cookie') From cb12f2a990b11e238acdd7eb7d9f9c68c3469cc6 Mon Sep 17 00:00:00 2001 From: u9g Date: Sun, 6 Sep 2026 16:36:42 -0400 Subject: [PATCH 5/5] Restate handshake comments as constraints --- src/client/cookies.js | 12 +++++------- src/client/play.js | 20 ++++++++------------ test/serverTest.js | 10 +++++----- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/client/cookies.js b/src/client/cookies.js index 16509df4..8badc9df 100644 --- a/src/client/cookies.js +++ b/src/client/cookies.js @@ -1,9 +1,8 @@ 'use strict' -// Cookies (1.20.5+) are keyed by resource location and answered from one map in every -// state, like the vanilla client's serverCookies; `cookie_request` and `cookie_response` -// only exist in the login, configuration and play states of versions that have cookies, -// so the listeners never fire (nor write) elsewhere. +// One map, keyed by resource location, must answer cookie requests in every state. +// `cookie_request` and `cookie_response` exist only in the login, configuration and play +// states of 1.20.5+, so the listeners never fire (nor write) elsewhere. module.exports = function (client, options) { client._cookies = new Map(options.cookies instanceof Map ? options.cookies : Object.entries(options.cookies ?? {})) @@ -14,9 +13,8 @@ module.exports = function (client, options) { client.on('cookie_request', (packet) => { let value = client._cookies.get(packet.cookie) if (value === undefined && !cookieValueIsOptional(client.version)) { - // An absent option and an empty ByteArray both serialize as a single 0x00 byte, so - // this reply is wire-identical to vanilla's null where minecraft-data (1.21.8) - // declares the value without the option wrapper + // Where minecraft-data (1.21.8) declares the value as a bare ByteArray, an empty one + // is wire-identical to an absent option: both serialize as a single 0x00 byte value = Buffer.alloc(0) } client.write('cookie_response', { key: packet.cookie, value }) diff --git a/src/client/play.js b/src/client/play.js index c3c6be84..d3e3fd09 100644 --- a/src/client/play.js +++ b/src/client/play.js @@ -11,8 +11,8 @@ module.exports = function (client, options) { } }) - // Every login packet starts a fresh chat session (new session UUID, message index 0) - // with the same profile key pair, as the vanilla client does after a reconfiguration + // Every login packet must start a fresh chat session (new session UUID, message + // index 0) with the same profile key pair client.on('login', (packet) => { if (packet.enforcesSecureChat) client.serverFeatures.enforcesSecureChat = packet.enforcesSecureChat const mcData = require('minecraft-data')(client.version) @@ -56,14 +56,10 @@ module.exports = function (client, options) { client.write('configuration_acknowledged', {}) } client.state = states.CONFIGURATION - // The vanilla client sends its brand and then Client Information right after - // login_acknowledged, and only those once: re-entering configuration from play - // is acknowledged with nothing else. Some servers (e.g. Hypixel) wait for the - // Client Information before sending finish_configuration and close the socket - // otherwise. Defaults are the vanilla ones and can be overridden per-field via - // the `clientSettings` option; a client that also sends Client Information in - // the play state (e.g. mineflayer on its 'login' event) still takes precedence - // there, exactly as the vanilla client re-sends settings. (#3623) + // 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', { @@ -83,8 +79,8 @@ module.exports = function (client, options) { particleStatus: clientSettings.particleStatus ?? 'all' }) } - // Vanilla answers with the server's packs it has locally; a pack in the reply makes - // the server skip that pack's registry entries, so nothing is known by default + // 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) => { const knownPacks = options.knownPacks ?? [] client.write('select_known_packs', { diff --git a/test/serverTest.js b/test/serverTest.js index 9d6d1b2e..96162f38 100644 --- a/test/serverTest.js +++ b/test/serverTest.js @@ -29,8 +29,8 @@ for (const supportedVersion of mc.supportedVersions) { let PORT const mcData = require('minecraft-data')(supportedVersion) const version = mcData.version - // minecraft-data 1.21.8 declares cookie_response.value as a non-optional ByteArray, so a - // stored cookie cannot be echoed there + // minecraft-data 1.21.8 declares cookie_response.value as a bare ByteArray, so an absent + // value cannot be sent there const hasCookies = 'packet_common_cookie_request' in mcData.protocol.types && mcData.protocol.types.packet_common_cookie_response[1][1].type[0] === 'option' @@ -586,7 +586,7 @@ for (const supportedVersion of mc.supportedVersions) { if (client.state !== mc.states.CONFIGURATION) return received.push({ settings: packet }) }) - // The nmp server does not implement re-configuration itself + // The nmp server does not handle configuration_acknowledged; the state is moved by hand client.on('configuration_acknowledged', () => { client.state = mc.states.CONFIGURATION client.once('finish_configuration', () => { @@ -669,8 +669,8 @@ for (const supportedVersion of mc.supportedVersions) { if (hasCookies) { it('answers login cookie requests from the cookies option', function (done) { const seeded = Buffer.from('seeded-cookie') - // A vanilla server only enables compression once the login cookie exchange is - // over, so drive the login state by hand instead of through mc.createServer + // Login cookies must be exchanged before set_compression; mc.createServer sends + // set_compression first, so the login state is driven by hand const server = net.createServer((socket) => { const client = new mc.Client(true, version.minecraftVersion) client.setSocket(socket)