* Initial changes for 1.20.2

* add NBT serialize tag type handling

* update tests

* Update pnbt and mcdata for nbt change

* lint

* fix wrong param to sizeOfNbt

* fix dupe NBT types

* move nbt logic to prismarine-nbt

* update tests

* update tests

* disable protodef validator in pluginChannel

* Fix state desync

* dump loginPacket.json in test output

* enable validation

* remove testing line in ci.yml

* update pnbt to 2.5.0

* update doc for `playerJoin`

* Update serializer.js

* update examples

* lint

* disable client bundle handling if bundle becomes too big

* Update client.js

* bump mcdata

* add soundSource and packedChunkPos example test values

---------

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
This commit is contained in:
extremeheat 2023-12-27 18:48:10 -05:00 committed by GitHub
parent 1740124c47
commit 112926da0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 212 additions and 178 deletions

View file

@ -89,6 +89,11 @@ Called when a client connects, but before any login has happened. Takes a
Called when a client is logged in against server. Takes a `Client` parameter.
### `playerJoin` event
Emitted after a player joins and enters the PLAY protocol state and can send and recieve game packets. This is emitted after the `login` event. On 1.20.2 and above after we emit the `login` event, the player will enter a CONFIG state, as opposed to the PLAY state (where game packets can be sent), so you must instead now wait for `playerJoin`.
### `listening` event
Called when the server is listening for connections. This means that the server is ready to accept incoming connections.
@ -261,6 +266,10 @@ Called when user authentication is resolved. Takes session data as parameter.
Called when the protocol changes state. Takes the new state and old state as
parameters.
### `playerJoin` event
Emitted after the player enters the PLAY protocol state and can send and recieve game packets
### `error` event
Called when an error occurs within the client. Takes an Error as parameter.

View file

@ -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.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.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.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)
* Parses all packets and emits events with packet fields as JavaScript
objects.
* Send a packet by supplying fields as a JavaScript object.
@ -115,6 +115,8 @@ const client = mc.createClient({
### Hello World server example
For a more up to date example, see examples/server/server.js.
```js
const mc = require('minecraft-protocol');
const server = mc.createServer({
@ -126,18 +128,12 @@ const server = mc.createServer({
});
const mcData = require('minecraft-data')(server.version)
server.on('login', function(client) {
server.on('playerJoin', function(client) {
const loginPacket = mcData.loginPacket
client.write('login', {
...loginPacket,
entityId: client.id,
isHardcore: false,
gameMode: 0,
previousGameMode: 255,
worldNames: loginPacket.worldNames,
dimensionCodec: loginPacket.dimensionCodec,
dimension: loginPacket.dimension,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
maxPlayers: server.maxPlayers,
viewDistance: 10,

View file

@ -11,7 +11,7 @@ const server = mc.createServer(options)
const mcData = require('minecraft-data')(server.version)
const loginPacket = mcData.loginPacket
server.on('login', function (client) {
server.on('playerJoin', function (client) {
broadcast(client.username + ' joined the game.')
const addr = client.socket.remoteAddress + ':' + client.socket.remotePort
console.log(client.username + ' connected', '(' + addr + ')')
@ -23,14 +23,11 @@ server.on('login', function (client) {
// send init data so client will start rendering world
client.write('login', {
...loginPacket,
entityId: client.id,
isHardcore: false,
gameMode: 0,
previousGameMode: 1,
worldNames: loginPacket.worldNames,
dimensionCodec: loginPacket.dimensionCodec,
dimension: loginPacket.dimension,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
maxPlayers: server.maxPlayers,
viewDistance: 10,
@ -48,11 +45,13 @@ server.on('login', function (client) {
flags: 0x00
})
client.on('chat', function (data) {
function handleChat (data) {
const message = '<' + client.username + '>' + ' ' + data.message
broadcast(message, null, client.username)
console.log(message)
})
}
client.on('chat', handleChat) // pre-1.19
client.on('chat_message', handleChat) // post 1.19
})
server.on('error', function (error) {
@ -63,27 +62,28 @@ server.on('listening', function () {
console.log('Server listening on port', server.socketServer.address().port)
})
function broadcast (message, exclude, username) {
let client
const translate = username ? 'chat.type.announcement' : 'chat.type.text'
username = username || 'Server'
for (const clientId in server.clients) {
if (server.clients[clientId] === undefined) continue
client = server.clients[clientId]
if (client !== exclude) {
const msg = {
translate,
with: [
username,
message
]
}
client.write('chat', {
message: JSON.stringify(msg),
position: 0,
sender: '0'
})
}
function sendBroadcastMessage (server, clients, message, sender) {
if (mcData.supportFeature('signedChat')) {
server.writeToClients(clients, 'player_chat', {
plainMessage: message,
signedChatContent: '',
unsignedChatContent: JSON.stringify({ text: message }),
type: 0,
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
senderName: JSON.stringify({ text: sender }),
senderTeam: undefined,
timestamp: Date.now(),
salt: 0n,
signature: mcData.supportFeature('useChatSessions') ? undefined : Buffer.alloc(0),
previousMessages: [],
filterType: 0,
networkName: JSON.stringify({ text: sender })
})
} else {
server.writeToClients(clients, 'chat', { message: JSON.stringify({ text: message }), position: 0, sender: sender || '0' })
}
}
function broadcast (message, exclude, username) {
sendBroadcastMessage(server, Object.values(server.clients).filter(client => client !== exclude), message)
}

View file

@ -8,18 +8,16 @@ const server = mc.createServer({
const mcData = require('minecraft-data')(server.version)
const loginPacket = mcData.loginPacket
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.registerChannel('minecraft:brand', ['string', []])
client.on('minecraft:brand', console.log)
client.write('login', {
...loginPacket,
entityId: client.id,
isHardcore: false,
gameMode: 0,
previousGameMode: 1,
worldNames: loginPacket.worldNames,
dimensionCodec: loginPacket.dimensionCodec,
dimension: loginPacket.dimension,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
maxPlayers: server.maxPlayers,

View file

@ -8,15 +8,13 @@ const server = mc.createServer({
const mcData = require('minecraft-data')(server.version)
const loginPacket = mcData.loginPacket
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.write('login', {
...loginPacket,
entityId: client.id,
isHardcore: false,
gameMode: 0,
previousGameMode: 1,
worldNames: loginPacket.worldNames,
dimensionCodec: loginPacket.dimensionCodec,
dimension: loginPacket.dimension,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
maxPlayers: server.maxPlayers,

View file

@ -9,7 +9,7 @@ const server = mc.createServer(options)
const mcData = require('minecraft-data')(server.version)
const loginPacket = mcData.loginPacket
server.on('login', function (client) {
server.on('playerJoin', function (client) {
const addr = client.socket.remoteAddress
console.log('Incoming connection', '(' + addr + ')')

View file

@ -22,15 +22,13 @@ for (let x = 0; x < 16; x++) {
}
}
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.write('login', {
...loginPacket,
entityId: client.id,
isHardcore: false,
gameMode: 0,
previousGameMode: 1,
worldNames: loginPacket.worldNames,
dimensionCodec: loginPacket.dimensionCodec,
dimension: loginPacket.dimension,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
maxPlayers: server.maxPlayers,

View file

@ -51,12 +51,12 @@
"endian-toggle": "^0.0.0",
"lodash.get": "^4.1.2",
"lodash.merge": "^4.3.0",
"minecraft-data": "^3.37.0",
"minecraft-data": "^3.53.0",
"minecraft-folder-path": "^1.2.0",
"node-fetch": "^2.6.1",
"node-rsa": "^0.4.2",
"prismarine-auth": "^2.2.0",
"prismarine-nbt": "^2.0.0",
"prismarine-nbt": "^2.5.0",
"prismarine-realms": "^1.2.0",
"protodef": "^1.8.0",
"readable-stream": "^4.1.0",

View file

@ -93,7 +93,7 @@ class Client extends EventEmitter {
const s = JSON.stringify(parsed.data, null, 2)
debug(s && s.length > 10000 ? parsed.data : s)
}
if (parsed.metadata.name === 'bundle_delimiter') {
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
if (this._mcBundle.length) { // End bundle
this._mcBundle.forEach(emitPacket)
emitPacket(parsed)
@ -103,6 +103,11 @@ class Client extends EventEmitter {
}
} else if (this._mcBundle.length) {
this._mcBundle.push(parsed)
if (this._mcBundle.length > 32) {
this._mcBundle.forEach(emitPacket)
this._mcBundle = []
this._hasBundlePacket = false
}
} else {
emitPacket(parsed)
}

View file

@ -32,30 +32,54 @@ module.exports = function (client, options) {
function onLogin (packet) {
const mcData = require('minecraft-data')(client.version)
client.state = states.PLAY
client.uuid = packet.uuid
client.username = packet.username
if (mcData.supportFeature('signedChat')) {
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
}
signedChatPlugin(client, options)
if (mcData.supportFeature('hasConfigurationState')) {
client.write('login_acknowledged', {})
enterConfigState()
// Server can tell client to re-enter config state
client.on('start_configuration', enterConfigState)
} else {
client.on('chat', (packet) => {
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
formattedMessage: packet.message,
sender: packet.sender,
positionId: packet.position,
verified: false
})
client.state = states.PLAY
onReady()
}
function enterConfigState () {
if (client.state === states.CONFIGURATION) return
client.state = states.CONFIGURATION
// Server should send finish_configuration on its own right after sending the client a dimension codec
// for login (that has data about world height, world gen, etc) after getting a login success from client
client.once('finish_configuration', () => {
client.write('finish_configuration', {})
client.state = states.PLAY
onReady()
})
}
function unsignedChat (message) {
client.write('chat', { message })
}
function onReady () {
client.emit('playerJoin')
if (mcData.supportFeature('signedChat')) {
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
}
signedChatPlugin(client, options)
} else {
client.on('chat', (packet) => {
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
formattedMessage: packet.message,
sender: packet.sender,
positionId: packet.position,
verified: false
})
})
}
client.chat = client._signedChat || unsignedChat
function unsignedChat (message) {
client.write('chat', { message })
}
client.chat = client._signedChat || unsignedChat
}
}
}

View file

@ -1,11 +1,13 @@
const ProtoDef = require('protodef').ProtoDef
const minecraft = require('../datatypes/minecraft')
const debug = require('debug')('minecraft-protocol')
const nbt = require('prismarine-nbt')
module.exports = function (client, options) {
const mcdata = require('minecraft-data')(options.version || require('../version').defaultVersion)
const channels = []
const proto = new ProtoDef(options.validateChannelProtocol ?? true)
nbt.addTypesToInterpreter('big', proto)
proto.addTypes(mcdata.protocol.types)
proto.addTypes(minecraft)
proto.addType('registerarr', [readDumbArr, writeDumbArr, sizeOfDumbArr])

View file

@ -37,7 +37,7 @@ module.exports = function (client, options) {
: client.profileKeys.signature
}
: null,
playerUUID: client.session?.selectedProfile?.id
playerUUID: client.session?.selectedProfile?.id ?? client.uuid
})
}
}

View file

@ -14,6 +14,7 @@ const tcpDns = require('./client/tcp_dns')
const autoVersion = require('./client/autoVersion')
const pluginChannels = require('./client/pluginChannels')
const versionChecking = require('./client/versionChecking')
const uuid = require('./datatypes/uuid')
module.exports = createClient
@ -54,6 +55,8 @@ function createClient (options) {
case 'offline':
default:
client.username = options.username
client.uuid = uuid.nameToMcOfflineUUID(client.username)
options.auth = 'offline'
options.connect(client)
break
}

View file

@ -46,6 +46,7 @@ function createServer (options = {}) {
server.onlineModeExceptions = Object.create(null)
server.favicon = favicon
server.options = options
options.registryCodec = options.registryCodec || mcData.registryCodec || mcData.loginPacket?.dimensionCodec
// The RSA keypair can take some time to generate
// and is only needed for online-mode

View file

@ -16,8 +16,6 @@ module.exports = {
size: buffer.length - offset
}
}],
nbt: ['native', minecraft.nbt[0]],
optionalNbt: ['native', minecraft.optionalNbt[0]],
compressedNbt: ['native', minecraft.compressedNbt[0]],
entityMetadataLoop: ['parametrizable', (compiler, { type, endVal }) => {
let code = 'let cursor = offset\n'
@ -55,8 +53,6 @@ module.exports = {
value.copy(buffer, offset)
return offset + value.length
}],
nbt: ['native', minecraft.nbt[1]],
optionalNbt: ['native', minecraft.optionalNbt[1]],
compressedNbt: ['native', minecraft.compressedNbt[1]],
entityMetadataLoop: ['parametrizable', (compiler, { type, endVal }) => {
let code = 'for (const i in value) {\n'
@ -84,8 +80,6 @@ module.exports = {
restBuffer: ['native', (value) => {
return value.length
}],
nbt: ['native', minecraft.nbt[2]],
optionalNbt: ['native', minecraft.optionalNbt[2]],
compressedNbt: ['native', minecraft.compressedNbt[2]],
entityMetadataLoop: ['parametrizable', (compiler, { type }) => {
let code = 'let size = 1\n'

View file

@ -8,8 +8,6 @@ const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint
module.exports = {
varlong: [readVarLong, writeVarLong, sizeOfVarLong],
UUID: [readUUID, writeUUID, 16],
nbt: [readNbt, writeNbt, sizeOfNbt],
optionalNbt: [readOptionalNbt, writeOptionalNbt, sizeOfOptionalNbt],
compressedNbt: [readCompressedNbt, writeCompressedNbt, sizeOfCompressedNbt],
restBuffer: [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
@ -43,35 +41,8 @@ function writeUUID (value, buffer, offset) {
return offset + 16
}
function readNbt (buffer, offset) {
return nbt.proto.read(buffer, offset, 'nbt')
}
function writeNbt (value, buffer, offset) {
return nbt.proto.write(value, buffer, offset, 'nbt')
}
function sizeOfNbt (value) {
return nbt.proto.sizeOf(value, 'nbt')
}
function readOptionalNbt (buffer, offset) {
if (offset + 1 > buffer.length) { throw new PartialReadError() }
if (buffer.readInt8(offset) === 0) return { size: 1 }
return nbt.proto.read(buffer, offset, 'nbt')
}
function writeOptionalNbt (value, buffer, offset) {
if (value === undefined) {
buffer.writeInt8(0, offset)
return offset + 1
}
return nbt.proto.write(value, buffer, offset, 'nbt')
}
function sizeOfOptionalNbt (value) {
if (value === undefined) { return 1 }
return nbt.proto.sizeOf(value, 'nbt')
function sizeOfNbt (value, { tagType } = { tagType: 'nbt' }) {
return nbt.proto.sizeOf(value, tagType)
}
// Length-prefixed compressed NBT, see differences: http://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753
@ -111,7 +82,7 @@ function writeCompressedNbt (value, buffer, offset) {
function sizeOfCompressedNbt (value) {
if (value === undefined) { return 2 }
const nbtBuffer = Buffer.alloc(sizeOfNbt(value, 'nbt'))
const nbtBuffer = Buffer.alloc(sizeOfNbt(value, { tagType: 'nbt' }))
nbt.proto.write(value, nbtBuffer, 0, 'nbt')
const compressedNbt = zlib.gzipSync(nbtBuffer) // TODO: async

18
src/datatypes/uuid.js Normal file
View file

@ -0,0 +1,18 @@
const crypto = require('crypto')
const UUID = require('uuid-1345')
// https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/UUID.java#L163
function javaUUID (s) {
const hash = crypto.createHash('md5')
hash.update(s, 'utf8')
const buffer = hash.digest()
buffer[6] = (buffer[6] & 0x0f) | 0x30
buffer[8] = (buffer[8] & 0x3f) | 0x80
return buffer
}
function nameToMcOfflineUUID (name) {
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
}
module.exports = { nameToMcOfflineUUID }

4
src/index.d.ts vendored
View file

@ -51,6 +51,8 @@ declare module 'minecraft-protocol' {
on(event: `raw.${string}`, handler: (buffer: Buffer, packetMeta: PacketMeta) => PromiseLike): this
on(event: 'playerChat', handler: (data: { formattedMessage: string, message: string, type: string, sender: string, senderName: string, senderTeam: string, verified?: boolean }) => PromiseLike): this
on(event: 'systemChat', handler: (data: { positionId: number, formattedMessage: string }) => PromiseLike): this
// Emitted after the player enters the PLAY state and can send and recieve game packets
on(event: 'playerJoin', handler: () => void): this
once(event: 'error', listener: (error: Error) => PromiseLike): this
once(event: 'packet', handler: (data: any, packetMeta: PacketMeta, buffer: Buffer, fullBuffer: Buffer) => PromiseLike): this
once(event: 'raw', handler: (buffer: Buffer, packetMeta: PacketMeta) => PromiseLike): this
@ -156,6 +158,8 @@ declare module 'minecraft-protocol' {
on(event: 'error', listener: (error: Error) => PromiseLike): this
on(event: 'login', handler: (client: ServerClient) => PromiseLike): this
on(event: 'listening', listener: () => PromiseLike): this
// Emitted after the player enters the PLAY state and can send and recieve game packets
on(event: 'playerJoin', handler: (client: ServerClient) => void): this
once(event: 'connection', handler: (client: ServerClient) => PromiseLike): this
once(event: 'error', listener: (error: Error) => PromiseLike): this
once(event: 'login', handler: (client: ServerClient) => PromiseLike): this

View file

@ -1,4 +1,4 @@
const UUID = require('uuid-1345')
const uuid = require('../datatypes/uuid')
const crypto = require('crypto')
const pluginChannels = require('../client/pluginChannels')
const states = require('../states')
@ -166,37 +166,28 @@ module.exports = function (client, server, options) {
}
}
// https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/UUID.java#L163
function javaUUID (s) {
const hash = crypto.createHash('md5')
hash.update(s, 'utf8')
const buffer = hash.digest()
buffer[6] = (buffer[6] & 0x0f) | 0x30
buffer[8] = (buffer[8] & 0x3f) | 0x80
return buffer
}
function nameToMcOfflineUUID (name) {
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
}
function loginClient () {
const isException = !!server.onlineModeExceptions[client.username.toLowerCase()]
if (onlineMode === false || isException) {
client.uuid = nameToMcOfflineUUID(client.username)
client.uuid = uuid.nameToMcOfflineUUID(client.username)
}
options.beforeLogin?.(client)
if (client.protocolVersion >= 27) { // 14w28a (27) added whole-protocol compression (http://wiki.vg/Protocol_History#14w28a), earlier versions per-packet compressed TODO: refactor into minecraft-data
client.write('compress', { threshold: 256 }) // Default threshold is 256
client.compressionThreshold = 256
}
// TODO: find out what properties are on 'success' packet
client.write('success', {
uuid: client.uuid,
username: client.username,
properties: []
})
// TODO: find out what properties are on 'success' packet
client.state = states.PLAY
if (client.supportFeature('hasConfigurationState')) {
client.once('login_acknowledged', onClientLoginAck)
} else {
client.state = states.PLAY
server.emit('playerJoin', client)
}
client.settings = {}
if (client.supportFeature('chainedChatWithHashing')) { // 1.19.1+
@ -217,6 +208,16 @@ module.exports = function (client, server, options) {
if (client.supportFeature('signedChat')) chatPlugin(client, server, options)
server.emit('login', client)
}
function onClientLoginAck () {
client.state = states.CONFIGURATION
client.write('registry_data', { codec: options.registryCodec || {} })
client.once('finish_configuration', () => {
client.state = states.PLAY
server.emit('playerJoin', client)
})
client.write('finish_configuration', {})
}
}
function mcPubKeyToPem (mcPubKeyBuffer) {

View file

@ -4,6 +4,7 @@ const states = {
HANDSHAKING: 'handshaking',
STATUS: 'status',
LOGIN: 'login',
CONFIGURATION: 'configuration',
PLAY: 'play'
}

View file

@ -5,6 +5,7 @@ const Serializer = require('protodef').Serializer
const Parser = require('protodef').FullPacketParser
const { ProtoDefCompiler } = require('protodef').Compiler
const nbt = require('prismarine-nbt')
const minecraft = require('../datatypes/minecraft')
const states = require('../states')
const merge = require('lodash.merge')
@ -30,6 +31,7 @@ function createProtocol (state, direction, version, customPackets, compiled = tr
const compiler = new ProtoDefCompiler()
compiler.addTypes(require('../datatypes/compiler-minecraft'))
compiler.addProtocol(merge(mcData.protocol, get(customPackets, [mcData.version.majorVersion])), [state, direction])
nbt.addTypesToCompiler('big', compiler)
const proto = compiler.compileProtoDefSync()
protocols[key] = proto
return proto
@ -38,6 +40,7 @@ function createProtocol (state, direction, version, customPackets, compiled = tr
const proto = new ProtoDef(false)
proto.addTypes(minecraft)
proto.addProtocol(merge(mcData.protocol, get(customPackets, [mcData.version.majorVersion])), [state, direction])
nbt.addTypesToInterperter('big', proto)
protocols[key] = proto
return proto
}

View file

@ -1,6 +1,6 @@
'use strict'
module.exports = {
defaultVersion: '1.20.1',
supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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']
defaultVersion: '1.20.2',
supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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']
}

View file

@ -2,6 +2,7 @@
const mc = require('../')
const os = require('os')
const fs = require('fs')
const path = require('path')
const assert = require('power-assert')
const util = require('util')
@ -20,7 +21,8 @@ for (const supportedVersion of mc.supportedVersions) {
const version = mcData.version
const MC_SERVER_JAR_DIR = process.env.MC_SERVER_JAR_DIR || os.tmpdir()
const MC_SERVER_JAR = MC_SERVER_JAR_DIR + '/minecraft_server.' + version.minecraftVersion + '.jar'
const wrap = new Wrap(MC_SERVER_JAR, MC_SERVER_PATH + '_' + supportedVersion, {
const MC_SERVER_DIR = MC_SERVER_PATH + '_' + supportedVersion
const wrap = new Wrap(MC_SERVER_JAR, MC_SERVER_DIR, {
minMem: 1024,
maxMem: 1024
})
@ -118,7 +120,23 @@ for (const supportedVersion of mc.supportedVersions) {
assert.strictEqual(packet.gameMode, 0)
client.chat('hello everyone; I have logged in.')
})
// Dump some data for easier debugging
client.on('raw.registry_data', (buffer) => {
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.bin', buffer)
})
client.on('registry_data', (json) => {
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.json', JSON.stringify(json))
})
client.on('login', (packet) => {
fs.writeFileSync(MC_SERVER_DIR + '_login.json', JSON.stringify(packet))
if (fs.existsSync(MC_SERVER_DIR + '_registry_data.json')) {
// generate a loginPacket.json for minecraft-data
fs.writeFileSync(MC_SERVER_DIR + '_loginPacket.json', JSON.stringify({
...packet,
dimensionCodec: JSON.parse(fs.readFileSync(MC_SERVER_DIR + '_registry_data.json')).codec
}, null, 2))
}
})
client.on('playerChat', function (data) {
chatCount += 1
assert.ok(chatCount <= 2)

View file

@ -35,6 +35,20 @@ const slotValue = {
}
}
const nbtValue = {
type: 'compound',
name: 'test',
value: {
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
}
}
const values = {
i32: 123456,
i16: -123,
@ -110,46 +124,12 @@ const values = {
f64: 99999.2222,
f32: -333.444,
slot: slotValue,
nbt: {
type: 'compound',
name: 'test',
value: {
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
}
},
optionalNbt: {
type: 'compound',
name: 'test',
value: {
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
}
},
nbt: nbtValue,
optionalNbt: nbtValue,
compressedNbt: nbtValue,
anonymousNbt: nbtValue,
anonOptionalNbt: nbtValue,
previousMessages: [],
compressedNbt: {
type: 'compound',
name: 'test',
value: {
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
}
},
i64: [0, 1],
u64: [0, 1],
entityMetadata: [
@ -223,6 +203,11 @@ const values = {
},
suggestionType: 'minecraft:summonable_entities'
}
},
soundSource: 'master',
packedChunkPos: {
x: 10,
z: 12
}
}

View file

@ -96,7 +96,7 @@ for (const supportedVersion of mc.supportedVersions) {
describe('mc-server ' + supportedVersion + 'v', function () {
this.timeout(5000)
this.beforeAll(async function () {
this.beforeEach(async function () {
PORT = await getPort()
console.log(`Using port for tests: ${PORT}`)
})
@ -299,7 +299,8 @@ for (const supportedVersion of mc.supportedVersions) {
const username = ['player1', 'player2']
let index = 0
server.on('login', function (client) {
server.on('playerJoin', function (client) {
console.log('ChatTest: Player has joined')
assert.notEqual(client.id, null)
assert.strictEqual(client.username, username[index++])
broadcast(client.username + ' joined the game.')
@ -322,8 +323,10 @@ for (const supportedVersion of mc.supportedVersions) {
version: version.minecraftVersion,
port: PORT
}))
console.log('ChatTest: Player1 is joining...')
player1.on('login', async function (packet) {
console.log('ChatTest: Player 1 has joined')
assert.strictEqual(packet.gameMode, 1)
const player2 = applyClientHelpers(mc.createClient({
username: 'player2',
@ -332,14 +335,16 @@ for (const supportedVersion of mc.supportedVersions) {
port: PORT
}))
console.log('ChatTest: waiting for next message from P2')
const p1Join = await player1.nextMessage('player2')
assert.strictEqual(p1Join, '{"text":"player2 joined the game."}')
console.log('ChatTest: Got message from P2')
player2.chat('hi')
const p2hi = await player1.nextMessage('player2')
assert.strictEqual(p2hi, '{"text":"<player2> hi"}')
console.log('ChatTest: Waiting again for next message from P2')
player1.chat('hello')
const p1hello = await player2.nextMessage('player1')
assert.strictEqual(p1hello, '{"text":"<player1> hello"}')
@ -389,7 +394,7 @@ for (const supportedVersion of mc.supportedVersions) {
port: PORT
})
let count = 2
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'ServerShutdown')
resolve()
@ -404,7 +409,7 @@ for (const supportedVersion of mc.supportedVersions) {
version: version.minecraftVersion,
port: PORT
})
client.on('login', function () {
client.on('playerJoin', function () {
server.close()
})
})
@ -420,7 +425,7 @@ for (const supportedVersion of mc.supportedVersions) {
version: version.minecraftVersion,
port: PORT
})
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.write('login', loginPacket(client, server))
})
server.on('close', done)
@ -459,7 +464,7 @@ for (const supportedVersion of mc.supportedVersions) {
version: version.minecraftVersion,
port: PORT
})
server.on('login', function (client) {
server.on('playerJoin', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'ServerShutdown')
})