From 2224d824065908e910520dfa8ea9f3f3ade242e4 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Wed, 4 Dec 2024 21:21:43 +0100 Subject: [PATCH 1/4] support 1.21.3 (#1347) * support 1.21.3 * Add bitflags, registryEntryHolder and registryEntryHolderSet types * Fix spacing in compiler types * Update compiler-minecraft.js * Fix registryEntryHolderSet read code (#1351) * Update ci.yml * Fix test for 1.21.3 (#1353) * Remove debug logging * Fix benchmark tests for 1.21.3 * Start updating packetTest for 1.21.3 * Update packetTest.js with new types * Fix minecraft-compiler * Speedup tests by setting world type to flat and disabling structures. * Didn't mean to commit that --------- Co-authored-by: extremeheat Co-authored-by: Grooble --- docs/README.md | 2 +- src/datatypes/compiler-minecraft.js | 159 +++++++++++++++++++++++++++- src/version.js | 2 +- test/benchmark.js | 17 +-- test/clientTest.js | 4 +- test/packetTest.js | 78 +++++++++++++- 6 files changed, 247 insertions(+), 15 deletions(-) diff --git a/docs/README.md b/docs/README.md index 97360021..f8cb78a3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,7 @@ Parse and serialize minecraft packets, plus authentication and encryption. * Supports Minecraft PC version 1.7.10, 1.8.8, 1.9 (15w40b, 1.9, 1.9.1-pre2, 1.9.2, 1.9.4), 1.10 (16w20a, 1.10-pre1, 1.10, 1.10.1, 1.10.2), 1.11 (16w35a, 1.11, 1.11.2), 1.12 (17w15a, 17w18b, 1.12-pre4, 1.12, 1.12.1, 1.12.2), and 1.13 (17w50a, 1.13, 1.13.1, 1.13.2-pre1, 1.13.2-pre2, 1.13.2), 1.14 (1.14, 1.14.1, 1.14.3, 1.14.4) - , 1.15 (1.15, 1.15.1, 1.15.2) and 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17 (21w07a, 1.17, 1.17.1), 1.18 (1.18, 1.18.1 and 1.18.2), 1.19 (1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21.1) + , 1.15 (1.15, 1.15.1, 1.15.2) and 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17 (21w07a, 1.17, 1.17.1), 1.18 (1.18, 1.18.1 and 1.18.2), 1.19 (1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4), 1.20 (1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6), 1.21 (1.21, 1.21.1, 1.21.3) * Parses all packets and emits events with packet fields as JavaScript objects. * Send a packet by supplying fields as a JavaScript object. diff --git a/src/datatypes/compiler-minecraft.js b/src/datatypes/compiler-minecraft.js index ba4db3e7..864b269f 100644 --- a/src/datatypes/compiler-minecraft.js +++ b/src/datatypes/compiler-minecraft.js @@ -1,3 +1,4 @@ +/* eslint-disable no-return-assign */ const UUID = require('uuid-1345') const minecraft = require('./minecraft') @@ -41,7 +42,7 @@ module.exports = { code += '}' return compiler.wrapCode(code) }], - arrayWithLengthOffset: ['parametrizable', (compiler, array) => { + arrayWithLengthOffset: ['parametrizable', (compiler, array) => { // TODO: remove let code = '' if (array.countType) { code += 'const { value: count, size: countSize } = ' + compiler.callType(array.countType) + '\n' @@ -61,6 +62,56 @@ module.exports = { code += '}\n' code += 'return { value: data, size }' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const { value: _value, size } = ${compiler.callType(type, 'offset')} + const value = { _value } + const flags = ${fstr} + for (const key in flags) { + value[key] = (_value & flags[key]) == flags[key] + } + return { value, size } + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + return compiler.wrapCode(` +const { value: n, size: nSize } = ${compiler.callType('varint')} +if (n !== 0) { + return { value: { ${opts.baseName}: n - 1 }, size: nSize } +} else { + const holder = ${compiler.callType(opts.otherwise.type)} + return { value: { ${opts.otherwise.name}: holder.data }, size: nSize + holder.size } +} + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + return compiler.wrapCode(` + const { value: n, size: nSize } = ${compiler.callType('varint')} + if (n === 0) { + const base = ${compiler.callType(opts.base.type, 'offset + nSize')} + return { value: { ${opts.base.name}: base.value }, size: base.size + nSize } + } else { + const set = [] + let accSize = nSize + for (let i = 0; i < n - 1; i++) { + const entry = ${compiler.callType(opts.otherwise.type, 'offset + accSize')} + set.push(entry.value) + accSize += entry.size + } + return { value: { ${opts.otherwise.name}: set }, size: accSize } + } + `.trim()) }] }, Write: { @@ -106,6 +157,58 @@ module.exports = { code += '}\n' code += 'return offset' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const flags = ${fstr} + let val = value._value ${big ? '|| 0n' : ''} + for (const key in flags) { + if (value[key]) val |= flags[key] + } + return (ctx.${type})(val, buffer, offset) + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.baseName}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +if (${baseName}) { + offset = ${compiler.callType(`${baseName} + 1`, 'varint')} +} else if (${otherwiseName}) { + offset = ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)} +} else { + throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set') +} +return offset + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.base.name}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +if (${baseName}) { + offset = ${compiler.callType(0, 'varint')} + offset = ${compiler.callType(`${baseName}`, opts.base.type)} +} else if (${otherwiseName}) { + offset = ${compiler.callType(`${otherwiseName}.length + 1`, 'varint')} + for (let i = 0; i < ${otherwiseName}.length; i++) { + offset = ${compiler.callType(`${otherwiseName}[i]`, opts.otherwise.type)} + } +} else { + throw new Error('registryEntryHolder type requires "${opts.base.name}" or "${opts.otherwise.name}" fields to be set') +} +return offset + `.trim()) }] }, SizeOf: { @@ -149,6 +252,60 @@ module.exports = { } code += 'return size' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const flags = ${fstr} + let val = value._value ${big ? '|| 0n' : ''} + for (const key in flags) { + if (value[key]) val |= flags[key] + } + return (ctx.${type})(val) + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.baseName}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +let size = 0 +if (${baseName}) { + size += ${compiler.callType(`${baseName} + 1`, 'varint')} +} else if (${otherwiseName}) { + size += ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)} +} else { + throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set') +} +return size + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.base.name}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +let size = 0 +if (${baseName}) { + size += ${compiler.callType(0, 'varint')} + size += ${compiler.callType(`${baseName}`, opts.base.type)} +} else if (${otherwiseName}) { + size += ${compiler.callType(`${otherwiseName}.length + 1`, 'varint')} + for (let i = 0; i < ${otherwiseName}.length; i++) { + size += ${compiler.callType(`${otherwiseName}[i]`, opts.otherwise.type)} + } +} else { + throw new Error('registryEntryHolder type requires "${opts.base.name}" or "${opts.otherwise.name}" fields to be set') +} +return size + `.trim()) }] } } diff --git a/src/version.js b/src/version.js index 327c242b..29d71e76 100644 --- a/src/version.js +++ b/src/version.js @@ -2,5 +2,5 @@ module.exports = { defaultVersion: '1.21.1', - supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1'] + supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3'] } diff --git a/test/benchmark.js b/test/benchmark.js index a4610371..1d24cd75 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -5,17 +5,18 @@ const ITERATIONS = 10000 const mc = require('../') const states = mc.states -const testDataWrite = [ - { name: 'keep_alive', params: { keepAliveId: 957759560 } }, - // TODO: 1.19+ `chat` -> `player_chat` feature toggle - // { name: 'chat', params: { message: ' Hello World!' } }, - { name: 'position_look', params: { x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true } } - // TODO: add more packets for better quality data -] - for (const supportedVersion of mc.supportedVersions) { const mcData = require('minecraft-data')(supportedVersion) const version = mcData.version + const positionFlags = mcData.isNewerOrEqualTo('1.21.3') ? { flags: { onGround: true, hasHorizontalCollision: false } } : { onGround: true } + const testDataWrite = [ + { name: 'keep_alive', params: { keepAliveId: 957759560 } }, + // TODO: 1.19+ `chat` -> `player_chat` feature toggle + // { name: 'chat', params: { message: ' Hello World!' } }, + { name: 'position_look', params: { x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, ...positionFlags } } + // TODO: add more packets for better quality data + ] + describe('benchmark ' + supportedVersion + 'v', function () { this.timeout(60 * 1000) const inputData = [] diff --git a/test/clientTest.js b/test/clientTest.js index 67eb8ece..ef46d346 100644 --- a/test/clientTest.js +++ b/test/clientTest.js @@ -59,7 +59,8 @@ for (const supportedVersion of mc.supportedVersions) { 'server-port': PORT, motd: 'test1234', 'max-players': 120, - // 'level-type': 'flat', + 'level-type': 'flat', + 'generate-structures': 'false', // 12m 'use-native-transport': 'false' // java 16 throws errors without this, https://www.spigotmc.org/threads/unable-to-access-address-of-buffer.311602 }, (err) => { if (err) reject(err) @@ -191,7 +192,6 @@ for (const supportedVersion of mc.supportedVersions) { } } else { // 1.19+ - console.log('Chat Message', data) const sender = JSON.parse(data.senderName) const msgPayload = data.formattedMessage ? JSON.parse(data.formattedMessage) : data.plainMessage const plainMessage = client.parseMessage(msgPayload).toString() diff --git a/test/packetTest.js b/test/packetTest.js index 534d28ed..7f89b95f 100644 --- a/test/packetTest.js +++ b/test/packetTest.js @@ -57,7 +57,32 @@ const nbtValue = { function getFixedPacketPayload (version, packetName) { if (packetName === 'declare_recipes') { - if (version['>=']('1.20.5')) { + if (version['>=']('1.21.3')) { + return { + recipes: [ + { + name: 'minecraft:campfire_input', + items: [ + 903, + 976 + ] + } + ], + stoneCutterRecipes: [ + { + input: { + ids: [ + 6 + ] + }, + slotDisplay: { + type: 'item_stack', + data: slotValue + } + } + ] + } + } else if (version['>=']('1.20.5')) { return { recipes: [ { @@ -241,6 +266,13 @@ const values = { suggestionType: 'minecraft:summonable_entities' } }, + bitflags: function (typeArgs, context) { + const results = {} + Object.keys(typeArgs.flags).forEach(function (index) { + results[typeArgs.flags[index]] = true + }) + return results + }, soundSource: 'master', packedChunkPos: { x: 10, @@ -263,7 +295,49 @@ const values = { isDebug: false, isFlat: false, portalCooldown: 0 - } + }, + MovementFlags: { + onGround: true, + hasHorizontalCollision: false + }, + ContainerID: 0, + PositionUpdateRelatives: { + x: true, + y: true, + z: true, + yaw: true, + pitch: true, + dx: true, + dy: true, + dz: true, + yawDelta: true + }, + RecipeDisplay: { + type: 'stonecutter', + data: { + ingredient: { type: 'empty' }, + result: { type: 'empty' }, + craftingStation: { type: 'empty' } + } + }, + SlotDisplay: { type: 'empty' }, + game_profile: { + name: 'test', + properties: [{ + key: 'foo', + value: 'bar' + }] + }, + optvarint: 1, + chat_session: { + uuid: '00112233-4455-6677-8899-aabbccddeeff', + publicKey: { + expireTime: 30, + keyBytes: [], + keySignature: [] + } + }, + IDSet: { ids: [2, 5] } } function getValue (_type, packet) { From d6b4e82eb170984380e7ea9f125ea5d72777bef2 Mon Sep 17 00:00:00 2001 From: u9g Date: Wed, 4 Dec 2024 15:22:11 -0500 Subject: [PATCH 2/4] Add type to serverKey in server (#1349) * add types to serverKey in server * don't change dep order --- package.json | 1 + src/index.d.ts | 2 ++ src/server/login.js | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/package.json b/package.json index 2eee53d5..11cbe1d1 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "prismarine-registry": "^1.8.0" }, "dependencies": { + "@types/node-rsa": "^1.1.4", "@types/readable-stream": "^4.0.0", "aes-js": "^3.1.2", "buffer-equal": "^1.0.0", diff --git a/src/index.d.ts b/src/index.d.ts index 42308525..e61d5403 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -7,6 +7,7 @@ import { Agent } from 'http' import { Transform } from "readable-stream"; import { BinaryLike, KeyObject } from 'crypto'; import { Realm } from "prismarine-realms" +import NodeRSA from 'node-rsa'; type PromiseLike = Promise | void @@ -166,6 +167,7 @@ declare module 'minecraft-protocol' { motd: string motdMsg?: Object favicon: string + serverKey: NodeRSA close(): void on(event: 'connection', handler: (client: ServerClient) => PromiseLike): this on(event: 'error', listener: (error: Error) => PromiseLike): this diff --git a/src/server/login.js b/src/server/login.js index ad143d38..858b952c 100644 --- a/src/server/login.js +++ b/src/server/login.js @@ -8,6 +8,11 @@ const { concat } = require('../transforms/binaryStream') const { mojangPublicKeyPem } = require('./constants') const debug = require('debug')('minecraft-protocol') +/** + * @param {import('../index').Client} client + * @param {import('../index').Server} server + * @param {Object} options + */ module.exports = function (client, server, options) { const mojangPubKey = crypto.createPublicKey(mojangPublicKeyPem) const raise = (translatableError) => client.end(translatableError, JSON.stringify({ translate: translatableError })) From f258c76b3a15badd902e82cd892168849444d79d Mon Sep 17 00:00:00 2001 From: rom1504bot Date: Wed, 4 Dec 2024 21:23:47 +0100 Subject: [PATCH 3/4] Release 1.51.0 (#1354) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/HISTORY.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/HISTORY.md b/docs/HISTORY.md index cc315a38..be234e79 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -1,5 +1,10 @@ # History +## 1.51.0 +* [Add type to serverKey in server (#1349)](https://github.com/PrismarineJS/node-minecraft-protocol/commit/d6b4e82eb170984380e7ea9f125ea5d72777bef2) (thanks @u9g) +* [support 1.21.3 (#1347)](https://github.com/PrismarineJS/node-minecraft-protocol/commit/2224d824065908e910520dfa8ea9f3f3ade242e4) (thanks @rom1504) +* [Bump @types/node from 20.16.15 to 22.7.9 (#1345)](https://github.com/PrismarineJS/node-minecraft-protocol/commit/590dc33fed2100e77ef58e7db716dfc45eb61159) (thanks @dependabot[bot]) + ## 1.50.0 * [1.21 Support (#1342)](https://github.com/PrismarineJS/node-minecraft-protocol/commit/5bebac36620d8f8ec256d19483e20e643d63de2a) (thanks @GroobleDierne) diff --git a/package.json b/package.json index 11cbe1d1..40a222ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-protocol", - "version": "1.50.0", + "version": "1.51.0", "description": "Parse and serialize minecraft packets, plus authentication and encryption.", "main": "src/index.js", "types": "src/index.d.ts", From 8e131c359ebd5509136fd849a82cc59cd0dc1e58 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Mon, 6 Jan 2025 13:47:44 -0500 Subject: [PATCH 4/4] Fix server_data payload for 1.19+, fix kicks messages on 1.20.3+ (#1364) * Fix server_data payload sent to clients for versions 1.19+ Fixes #1362 Add missing fields to `server_data` payload for versions 1.19+. * Add `motd` and `icon` fields to `server_data` payload for version 1.19. * Add `motd`, `icon`, and `enforcesSecureChat` fields to `server_data` payload for version 1.19.2. * Add `motd`, `icon`, and `enforcesSecureChat` fields to `server_data` payload for version 1.19.3. * Add `motd`, `iconBytes`, and `enforcesSecureChat` fields to `server_data` payload for versions 1.19.4, 1.20, and 1.20.2. * Add `motd`, `iconBytes`, and `enforcesSecureChat` fields to `server_data` payload for version 1.20.3. * Add `motd` and `iconBytes` fields to `server_data` payload for version 1.20.5+. * Use NBT components for `motd` if `chatPacketsUseNbtComponents` feature is supported. * Convert `favicon` to buffer for `iconBytes` field if available. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/PrismarineJS/node-minecraft-protocol/issues/1362?shareId=XXXX-XXXX-XXXX-XXXX). * add --retries 2 * lint, debug close event emit twice * bail tests * fix * flaky test fix * remove debug * Update serverTest.js * Fix NBT chat not being used for 1.20.3+ kicks * Update createClient.js * fix client.._supportFeature not being defined * only nbt on the play state disconnect --- src/client.js | 1 + src/createServer.js | 1 + src/server.js | 8 ++++++-- src/server/login.js | 6 ++++++ test/serverTest.js | 15 +++++++++++++-- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index 5b63c295..74749698 100644 --- a/src/client.js +++ b/src/client.js @@ -30,6 +30,7 @@ class Client extends EventEmitter { this.hideErrors = hideErrors this.closeTimer = null const mcData = require('minecraft-data')(version) + this._supportFeature = mcData.supportFeature this.state = states.HANDSHAKING this._hasBundlePacket = mcData.supportFeature('hasBundlePacket') } diff --git a/src/createServer.js b/src/createServer.js index 4a56c0ad..50ae0a38 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -46,6 +46,7 @@ function createServer (options = {}) { server.onlineModeExceptions = Object.create(null) server.favicon = favicon server.options = options + server._supportFeature = mcData.supportFeature options.registryCodec = options.registryCodec || mcData.registryCodec || mcData.loginPacket?.dimensionCodec // The RSA keypair can take some time to generate diff --git a/src/server.js b/src/server.js index 99cfcbac..46df1042 100644 --- a/src/server.js +++ b/src/server.js @@ -4,6 +4,7 @@ const net = require('net') const EventEmitter = require('events').EventEmitter const Client = require('./client') const states = require('./states') +const nbt = require('prismarine-nbt') const { createSerializer } = require('./transforms/serializer') class Server extends EventEmitter { @@ -26,11 +27,14 @@ class Server extends EventEmitter { self.socketServer.on('connection', socket => { const client = new Client(true, this.version, this.customPackets, this.hideErrors) client._end = client.end - client.end = function end (endReason, fullReason = JSON.stringify({ text: endReason })) { + client.end = function end (endReason, fullReason) { if (client.state === states.PLAY) { + fullReason ||= this._supportFeature('chatPacketsUseNbtComponents') + ? nbt.comp({ text: nbt.string(endReason) }) + : JSON.stringify({ text: endReason }) client.write('kick_disconnect', { reason: fullReason }) } else if (client.state === states.LOGIN) { - client.write('disconnect', { reason: fullReason }) + client.write('disconnect', { reason: fullReason || endReason }) } client._end(endReason) } diff --git a/src/server/login.js b/src/server/login.js index 858b952c..bc578511 100644 --- a/src/server/login.js +++ b/src/server/login.js @@ -7,6 +7,7 @@ const chatPlugin = require('./chat') const { concat } = require('../transforms/binaryStream') const { mojangPublicKeyPem } = require('./constants') const debug = require('debug')('minecraft-protocol') +const nbt = require('prismarine-nbt') /** * @param {import('../index').Client} client @@ -196,7 +197,12 @@ module.exports = function (client, server, options) { client.settings = {} if (client.supportFeature('chainedChatWithHashing')) { // 1.19.1+ + const jsonMotd = JSON.stringify(server.motdMsg ?? { text: server.motd }) + const nbtMotd = nbt.comp({ text: nbt.string(server.motd) }) client.write('server_data', { + motd: client.supportFeature('chatPacketsUseNbtComponents') ? nbtMotd : jsonMotd, + icon: server.favicon, // b64 + iconBytes: server.favicon ? Buffer.from(server.favicon, 'base64') : undefined, previewsChat: options.enableChatPreview, // Note: in 1.20.5+ user must send this with `login` enforcesSecureChat: options.enforceSecureProfile diff --git a/test/serverTest.js b/test/serverTest.js index 5837e765..19760342 100644 --- a/test/serverTest.js +++ b/test/serverTest.js @@ -102,6 +102,7 @@ for (const supportedVersion of mc.supportedVersions) { describe('mc-server ' + supportedVersion + 'v', function () { this.timeout(5000) this.beforeEach(async function () { + console.log('🔻 Starting test', this.currentTest.title) PORT = await getPort() console.log(`Using port for tests: ${PORT}`) }) @@ -411,9 +412,12 @@ for (const supportedVersion of mc.supportedVersions) { }) }) function checkFinish () { - if (serverPlayerDisconnected && clientClosed && serverClosed) done() + if (serverPlayerDisconnected && clientClosed && serverClosed) { + console.log('Kick test is done') + callOnce(done) + } } - }) + }).retries(2) it('gives correct reason for kicking clients when shutting down', function (done) { const server = mc.createServer({ @@ -532,3 +536,10 @@ for (const supportedVersion of mc.supportedVersions) { }) }) } + +function callOnce (fn, ...args) { + console.log('Call Fn', fn.called) + if (fn.called) return + fn(...args) + fn.called = true +}