Skip to content

Commit 112926d

Browse files
extremeheatrom1504
andauthored
Pc1.20.2 (#1265)
* 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]>
1 parent 1740124 commit 112926d

25 files changed

+212
-178
lines changed

docs/API.md

+9
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ Called when a client connects, but before any login has happened. Takes a
8989

9090
Called when a client is logged in against server. Takes a `Client` parameter.
9191

92+
### `playerJoin` event
93+
94+
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`.
95+
96+
9297
### `listening` event
9398

9499
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.
261266
Called when the protocol changes state. Takes the new state and old state as
262267
parameters.
263268

269+
### `playerJoin` event
270+
271+
Emitted after the player enters the PLAY protocol state and can send and recieve game packets
272+
264273
### `error` event
265274

266275
Called when an error occurs within the client. Takes an Error as parameter.

docs/README.md

+5-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Parse and serialize minecraft packets, plus authentication and encryption.
1313

1414
* 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),
1515
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)
16-
, 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)
16+
, 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)
1717
* Parses all packets and emits events with packet fields as JavaScript
1818
objects.
1919
* Send a packet by supplying fields as a JavaScript object.
@@ -115,6 +115,8 @@ const client = mc.createClient({
115115

116116
### Hello World server example
117117

118+
For a more up to date example, see examples/server/server.js.
119+
118120
```js
119121
const mc = require('minecraft-protocol');
120122
const server = mc.createServer({
@@ -126,18 +128,12 @@ const server = mc.createServer({
126128
});
127129
const mcData = require('minecraft-data')(server.version)
128130

129-
server.on('login', function(client) {
131+
server.on('playerJoin', function(client) {
130132
const loginPacket = mcData.loginPacket
131133

132134
client.write('login', {
135+
...loginPacket,
133136
entityId: client.id,
134-
isHardcore: false,
135-
gameMode: 0,
136-
previousGameMode: 255,
137-
worldNames: loginPacket.worldNames,
138-
dimensionCodec: loginPacket.dimensionCodec,
139-
dimension: loginPacket.dimension,
140-
worldName: 'minecraft:overworld',
141137
hashedSeed: [0, 0],
142138
maxPlayers: server.maxPlayers,
143139
viewDistance: 10,

examples/server/server.js

+29-29
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const server = mc.createServer(options)
1111
const mcData = require('minecraft-data')(server.version)
1212
const loginPacket = mcData.loginPacket
1313

14-
server.on('login', function (client) {
14+
server.on('playerJoin', function (client) {
1515
broadcast(client.username + ' joined the game.')
1616
const addr = client.socket.remoteAddress + ':' + client.socket.remotePort
1717
console.log(client.username + ' connected', '(' + addr + ')')
@@ -23,14 +23,11 @@ server.on('login', function (client) {
2323

2424
// send init data so client will start rendering world
2525
client.write('login', {
26+
...loginPacket,
2627
entityId: client.id,
2728
isHardcore: false,
2829
gameMode: 0,
2930
previousGameMode: 1,
30-
worldNames: loginPacket.worldNames,
31-
dimensionCodec: loginPacket.dimensionCodec,
32-
dimension: loginPacket.dimension,
33-
worldName: 'minecraft:overworld',
3431
hashedSeed: [0, 0],
3532
maxPlayers: server.maxPlayers,
3633
viewDistance: 10,
@@ -48,11 +45,13 @@ server.on('login', function (client) {
4845
flags: 0x00
4946
})
5047

51-
client.on('chat', function (data) {
48+
function handleChat (data) {
5249
const message = '<' + client.username + '>' + ' ' + data.message
5350
broadcast(message, null, client.username)
5451
console.log(message)
55-
})
52+
}
53+
client.on('chat', handleChat) // pre-1.19
54+
client.on('chat_message', handleChat) // post 1.19
5655
})
5756

5857
server.on('error', function (error) {
@@ -63,27 +62,28 @@ server.on('listening', function () {
6362
console.log('Server listening on port', server.socketServer.address().port)
6463
})
6564

66-
function broadcast (message, exclude, username) {
67-
let client
68-
const translate = username ? 'chat.type.announcement' : 'chat.type.text'
69-
username = username || 'Server'
70-
for (const clientId in server.clients) {
71-
if (server.clients[clientId] === undefined) continue
72-
73-
client = server.clients[clientId]
74-
if (client !== exclude) {
75-
const msg = {
76-
translate,
77-
with: [
78-
username,
79-
message
80-
]
81-
}
82-
client.write('chat', {
83-
message: JSON.stringify(msg),
84-
position: 0,
85-
sender: '0'
86-
})
87-
}
65+
function sendBroadcastMessage (server, clients, message, sender) {
66+
if (mcData.supportFeature('signedChat')) {
67+
server.writeToClients(clients, 'player_chat', {
68+
plainMessage: message,
69+
signedChatContent: '',
70+
unsignedChatContent: JSON.stringify({ text: message }),
71+
type: 0,
72+
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
73+
senderName: JSON.stringify({ text: sender }),
74+
senderTeam: undefined,
75+
timestamp: Date.now(),
76+
salt: 0n,
77+
signature: mcData.supportFeature('useChatSessions') ? undefined : Buffer.alloc(0),
78+
previousMessages: [],
79+
filterType: 0,
80+
networkName: JSON.stringify({ text: sender })
81+
})
82+
} else {
83+
server.writeToClients(clients, 'chat', { message: JSON.stringify({ text: message }), position: 0, sender: sender || '0' })
8884
}
8985
}
86+
87+
function broadcast (message, exclude, username) {
88+
sendBroadcastMessage(server, Object.values(server.clients).filter(client => client !== exclude), message)
89+
}

examples/server_channel/server_channel.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@ const server = mc.createServer({
88
const mcData = require('minecraft-data')(server.version)
99
const loginPacket = mcData.loginPacket
1010

11-
server.on('login', function (client) {
11+
server.on('playerJoin', function (client) {
1212
client.registerChannel('minecraft:brand', ['string', []])
1313
client.on('minecraft:brand', console.log)
1414

1515
client.write('login', {
16+
...loginPacket,
1617
entityId: client.id,
1718
isHardcore: false,
1819
gameMode: 0,
1920
previousGameMode: 1,
20-
worldNames: loginPacket.worldNames,
21-
dimensionCodec: loginPacket.dimensionCodec,
22-
dimension: loginPacket.dimension,
2321
worldName: 'minecraft:overworld',
2422
hashedSeed: [0, 0],
2523
maxPlayers: server.maxPlayers,

examples/server_custom_channel/server_custom_channel.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ const server = mc.createServer({
88
const mcData = require('minecraft-data')(server.version)
99
const loginPacket = mcData.loginPacket
1010

11-
server.on('login', function (client) {
11+
server.on('playerJoin', function (client) {
1212
client.write('login', {
13+
...loginPacket,
1314
entityId: client.id,
1415
isHardcore: false,
1516
gameMode: 0,
1617
previousGameMode: 1,
17-
worldNames: loginPacket.worldNames,
18-
dimensionCodec: loginPacket.dimensionCodec,
19-
dimension: loginPacket.dimension,
2018
worldName: 'minecraft:overworld',
2119
hashedSeed: [0, 0],
2220
maxPlayers: server.maxPlayers,

examples/server_helloworld/server_helloworld.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const server = mc.createServer(options)
99
const mcData = require('minecraft-data')(server.version)
1010
const loginPacket = mcData.loginPacket
1111

12-
server.on('login', function (client) {
12+
server.on('playerJoin', function (client) {
1313
const addr = client.socket.remoteAddress
1414
console.log('Incoming connection', '(' + addr + ')')
1515

examples/server_world/mc.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@ for (let x = 0; x < 16; x++) {
2222
}
2323
}
2424

25-
server.on('login', function (client) {
25+
server.on('playerJoin', function (client) {
2626
client.write('login', {
27+
...loginPacket,
2728
entityId: client.id,
2829
isHardcore: false,
2930
gameMode: 0,
3031
previousGameMode: 1,
31-
worldNames: loginPacket.worldNames,
32-
dimensionCodec: loginPacket.dimensionCodec,
33-
dimension: loginPacket.dimension,
3432
worldName: 'minecraft:overworld',
3533
hashedSeed: [0, 0],
3634
maxPlayers: server.maxPlayers,

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@
5151
"endian-toggle": "^0.0.0",
5252
"lodash.get": "^4.1.2",
5353
"lodash.merge": "^4.3.0",
54-
"minecraft-data": "^3.37.0",
54+
"minecraft-data": "^3.53.0",
5555
"minecraft-folder-path": "^1.2.0",
5656
"node-fetch": "^2.6.1",
5757
"node-rsa": "^0.4.2",
5858
"prismarine-auth": "^2.2.0",
59-
"prismarine-nbt": "^2.0.0",
59+
"prismarine-nbt": "^2.5.0",
6060
"prismarine-realms": "^1.2.0",
6161
"protodef": "^1.8.0",
6262
"readable-stream": "^4.1.0",

src/client.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class Client extends EventEmitter {
9393
const s = JSON.stringify(parsed.data, null, 2)
9494
debug(s && s.length > 10000 ? parsed.data : s)
9595
}
96-
if (parsed.metadata.name === 'bundle_delimiter') {
96+
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
9797
if (this._mcBundle.length) { // End bundle
9898
this._mcBundle.forEach(emitPacket)
9999
emitPacket(parsed)
@@ -103,6 +103,11 @@ class Client extends EventEmitter {
103103
}
104104
} else if (this._mcBundle.length) {
105105
this._mcBundle.push(parsed)
106+
if (this._mcBundle.length > 32) {
107+
this._mcBundle.forEach(emitPacket)
108+
this._mcBundle = []
109+
this._hasBundlePacket = false
110+
}
106111
} else {
107112
emitPacket(parsed)
108113
}

src/client/play.js

+41-17
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,54 @@ module.exports = function (client, options) {
3232

3333
function onLogin (packet) {
3434
const mcData = require('minecraft-data')(client.version)
35-
client.state = states.PLAY
3635
client.uuid = packet.uuid
3736
client.username = packet.username
3837

39-
if (mcData.supportFeature('signedChat')) {
40-
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
41-
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
42-
}
43-
signedChatPlugin(client, options)
38+
if (mcData.supportFeature('hasConfigurationState')) {
39+
client.write('login_acknowledged', {})
40+
enterConfigState()
41+
// Server can tell client to re-enter config state
42+
client.on('start_configuration', enterConfigState)
4443
} else {
45-
client.on('chat', (packet) => {
46-
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
47-
formattedMessage: packet.message,
48-
sender: packet.sender,
49-
positionId: packet.position,
50-
verified: false
51-
})
52-
})
44+
client.state = states.PLAY
45+
onReady()
5346
}
5447

55-
function unsignedChat (message) {
56-
client.write('chat', { message })
48+
function enterConfigState () {
49+
if (client.state === states.CONFIGURATION) return
50+
client.state = states.CONFIGURATION
51+
// Server should send finish_configuration on its own right after sending the client a dimension codec
52+
// for login (that has data about world height, world gen, etc) after getting a login success from client
53+
client.once('finish_configuration', () => {
54+
client.write('finish_configuration', {})
55+
client.state = states.PLAY
56+
onReady()
57+
})
5758
}
5859

59-
client.chat = client._signedChat || unsignedChat
60+
function onReady () {
61+
client.emit('playerJoin')
62+
if (mcData.supportFeature('signedChat')) {
63+
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
64+
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
65+
}
66+
signedChatPlugin(client, options)
67+
} else {
68+
client.on('chat', (packet) => {
69+
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
70+
formattedMessage: packet.message,
71+
sender: packet.sender,
72+
positionId: packet.position,
73+
verified: false
74+
})
75+
})
76+
}
77+
78+
function unsignedChat (message) {
79+
client.write('chat', { message })
80+
}
81+
82+
client.chat = client._signedChat || unsignedChat
83+
}
6084
}
6185
}

src/client/pluginChannels.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const ProtoDef = require('protodef').ProtoDef
22
const minecraft = require('../datatypes/minecraft')
33
const debug = require('debug')('minecraft-protocol')
4+
const nbt = require('prismarine-nbt')
45

56
module.exports = function (client, options) {
67
const mcdata = require('minecraft-data')(options.version || require('../version').defaultVersion)
78
const channels = []
89
const proto = new ProtoDef(options.validateChannelProtocol ?? true)
10+
nbt.addTypesToInterpreter('big', proto)
911
proto.addTypes(mcdata.protocol.types)
1012
proto.addTypes(minecraft)
1113
proto.addType('registerarr', [readDumbArr, writeDumbArr, sizeOfDumbArr])

src/client/setProtocol.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module.exports = function (client, options) {
3737
: client.profileKeys.signature
3838
}
3939
: null,
40-
playerUUID: client.session?.selectedProfile?.id
40+
playerUUID: client.session?.selectedProfile?.id ?? client.uuid
4141
})
4242
}
4343
}

src/createClient.js

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const tcpDns = require('./client/tcp_dns')
1414
const autoVersion = require('./client/autoVersion')
1515
const pluginChannels = require('./client/pluginChannels')
1616
const versionChecking = require('./client/versionChecking')
17+
const uuid = require('./datatypes/uuid')
1718

1819
module.exports = createClient
1920

@@ -54,6 +55,8 @@ function createClient (options) {
5455
case 'offline':
5556
default:
5657
client.username = options.username
58+
client.uuid = uuid.nameToMcOfflineUUID(client.username)
59+
options.auth = 'offline'
5760
options.connect(client)
5861
break
5962
}

src/createServer.js

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ function createServer (options = {}) {
4646
server.onlineModeExceptions = Object.create(null)
4747
server.favicon = favicon
4848
server.options = options
49+
options.registryCodec = options.registryCodec || mcData.registryCodec || mcData.loginPacket?.dimensionCodec
4950

5051
// The RSA keypair can take some time to generate
5152
// and is only needed for online-mode

0 commit comments

Comments
 (0)