Skip to content

Commit

Permalink
Pc1.20.2 (#1265)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
extremeheat and rom1504 authored Dec 27, 2023
1 parent 1740124 commit 112926d
Show file tree
Hide file tree
Showing 25 changed files with 212 additions and 178 deletions.
9 changes: 9 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 5 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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({
Expand All @@ -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,
Expand Down
58 changes: 29 additions & 29 deletions examples/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 + ')')
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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)
}
6 changes: 2 additions & 4 deletions examples/server_channel/server_channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 2 additions & 4 deletions examples/server_custom_channel/server_custom_channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion examples/server_helloworld/server_helloworld.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 + ')')

Expand Down
6 changes: 2 additions & 4 deletions examples/server_world/mc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
58 changes: 41 additions & 17 deletions src/client/play.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 unsignedChat (message) {
client.write('chat', { message })
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()
})
}

client.chat = client._signedChat || unsignedChat
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
})
})
}

function unsignedChat (message) {
client.write('chat', { message })
}

client.chat = client._signedChat || unsignedChat
}
}
}
2 changes: 2 additions & 0 deletions src/client/pluginChannels.js
Original file line number Diff line number Diff line change
@@ -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])
Expand Down
2 changes: 1 addition & 1 deletion src/client/setProtocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/createClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions src/createServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

2 comments on commit 112926d

@sebseb7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what I get when trying to login using 1.20.2

[electron] TypeError: Cannot read properties of undefined (reading 'overworld')
[electron]     at handleRespawnPacketData (C:\Users\Seb\Desktop\bot\afktool\node_modules\mineflayer\lib\plugins\game.js:46:63)
[electron]     at Client.<anonymous> (C:\Users\Seb\Desktop\bot\afktool\node_modules\mineflayer\lib\plugins\game.js:75:5)[electron]     at Client.emit (node:events:526:35)
[electron]     at emitPacket (C:\Users\Seb\Desktop\bot\afktool\node_modules\minecraft-protocol\src\client.js:83:12)
[electron]     at FullPacketParser.<anonymous> (C:\Users\Seb\Desktop\bot\afktool\node_modules\minecraft-protocol\src\client.js:112:9)
[electron]     at FullPacketParser.emit (node:events:514:28)
[electron]     at addChunk (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:279:12)
[electron]     at readableAddChunk (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:262:11)
[electron]     at Readable.push (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:228:10)
[electron]     at Transform.push (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_transform.js:132:32)
[electron] TypeError: Cannot read properties of undefined (reading 'the_end')
[electron]     at handleRespawnPacketData (C:\Users\Seb\Desktop\bot\afktool\node_modules\mineflayer\lib\plugins\game.js:46:63)
[electron]     at Client.<anonymous> (C:\Users\Seb\Desktop\bot\afktool\node_modules\mineflayer\lib\plugins\game.js:75:5)
[electron]     at Client.emit (node:events:526:35)
[electron]     at emitPacket (C:\Users\Seb\Desktop\bot\afktool\node_modules\minecraft-protocol\src\client.js:83:12)
[electron]     at FullPacketParser.<anonymous> (C:\Users\Seb\Desktop\bot\afktool\node_modules\minecraft-protocol\src\client.js:112:9)
[electron]     at FullPacketParser.emit (node:events:514:28)
[electron]     at addChunk (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:279:12)
[electron]     at readableAddChunk (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:262:11)
[electron]     at Readable.push (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_readable.js:228:10)
[electron]     at Transform.push (C:\Users\Seb\Desktop\bot\afktool\node_modules\protodef\node_modules\readable-stream\lib\_stream_transform.js:132:32)

@rom1504
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mineflayer does not support 1.20.2 yet

Please sign in to comment.