Skip to content

Commit 1d9a382

Browse files
wgaylordrom1504extremeheat
authored
1.20.3 / 1.20.4 support (#1275)
* Inital 1.20.3 update, bring in minecraft-data with 1.20.3 support and add it to the versions * Add data for packetTest for explosion packet. * Add 1.20.4 to version list. * Add fix for 1.20.3+ NBT isntead of strings for chat. Not happy with this but it does work. * Fix linting * Update version.js Remove 1.20.3 since its the same as 1.20.4. (As suggested) * Comment how handleNBTStrings works. * Removed debug console.log * Update README.md * chat packet nbt handling fix, use feature * big endian UUID, add back `text` wrapper * use prismarine-chat in client test * expose _handleNbtComponent * use prismarine-chat exposed processNbtMessage * fix pre-1.20.4 * Update package.json * Update server.js * Update server.js add missing import * update server hello world --------- Co-authored-by: Romain Beaumont <[email protected]> Co-authored-by: extremeheat <[email protected]>
1 parent f97a236 commit 1d9a382

File tree

11 files changed

+126
-35
lines changed

11 files changed

+126
-35
lines changed

docs/API.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ The client's protocol version
238238

239239
### client.version
240240

241-
The client's version
241+
The client's version, as a string
242242

243243
### `packet` event
244244

docs/README.md

+36-12
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.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)
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.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 and 1.20.4)
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.
@@ -118,16 +118,23 @@ const client = mc.createClient({
118118
For a more up to date example, see examples/server/server.js.
119119

120120
```js
121-
const mc = require('minecraft-protocol');
121+
const mc = require('minecraft-protocol')
122+
const nbt = require('prismarine-nbt')
122123
const server = mc.createServer({
123124
'online-mode': true, // optional
124125
encryption: true, // optional
125126
host: '0.0.0.0', // optional
126127
port: 25565, // optional
127-
version: '1.16.3'
128-
});
128+
version: '1.20.4'
129+
})
129130
const mcData = require('minecraft-data')(server.version)
130131

132+
function chatText (text) {
133+
return mcData.supportFeature('chatPacketsUseNbtComponents')
134+
? nbt.comp({ text: nbt.string(text) })
135+
: JSON.stringify({ text })
136+
}
137+
131138
server.on('playerJoin', function(client) {
132139
const loginPacket = mcData.loginPacket
133140

@@ -141,7 +148,7 @@ server.on('playerJoin', function(client) {
141148
enableRespawnScreen: true,
142149
isDebug: false,
143150
isFlat: false
144-
});
151+
})
145152

146153
client.write('position', {
147154
x: 0,
@@ -150,18 +157,35 @@ server.on('playerJoin', function(client) {
150157
yaw: 0,
151158
pitch: 0,
152159
flags: 0x00
153-
});
160+
})
154161

155-
const msg = {
162+
const message = {
156163
translate: 'chat.type.announcement',
157-
"with": [
164+
with: [
158165
'Server',
159166
'Hello, world!'
160167
]
161-
};
162-
163-
client.write("chat", { message: JSON.stringify(msg), position: 0, sender: '0' });
164-
});
168+
}
169+
if (mcData.supportFeature('signedChat')) {
170+
client.write('player_chat', {
171+
plainMessage: message,
172+
signedChatContent: '',
173+
unsignedChatContent: chatText(message),
174+
type: 0,
175+
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
176+
senderName: JSON.stringify({ text: 'me' }),
177+
senderTeam: undefined,
178+
timestamp: Date.now(),
179+
salt: 0n,
180+
signature: mcData.supportFeature('useChatSessions') ? undefined : Buffer.alloc(0),
181+
previousMessages: [],
182+
filterType: 0,
183+
networkName: JSON.stringify({ text: 'me' })
184+
})
185+
} else {
186+
client.write('chat', { message: JSON.stringify({ text: message }), position: 0, sender: 'me' })
187+
}
188+
})
165189
```
166190

167191
## Testing

examples/server/server.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const mc = require('minecraft-protocol')
2+
const nbt = require('prismarine-nbt')
23

34
const options = {
45
motd: 'Vox Industries',
@@ -10,6 +11,11 @@ const options = {
1011
const server = mc.createServer(options)
1112
const mcData = require('minecraft-data')(server.version)
1213
const loginPacket = mcData.loginPacket
14+
function chatText (text) {
15+
return mcData.supportFeature('chatPacketsUseNbtComponents')
16+
? nbt.comp({ text: nbt.string(text) })
17+
: JSON.stringify({ text })
18+
}
1319

1420
server.on('playerJoin', function (client) {
1521
broadcast(client.username + ' joined the game.')
@@ -67,7 +73,7 @@ function sendBroadcastMessage (server, clients, message, sender) {
6773
server.writeToClients(clients, 'player_chat', {
6874
plainMessage: message,
6975
signedChatContent: '',
70-
unsignedChatContent: JSON.stringify({ text: message }),
76+
unsignedChatContent: chatText(message),
7177
type: 0,
7278
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
7379
senderName: JSON.stringify({ text: sender }),

examples/server_helloworld/server_helloworld.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ const options = {
88
const server = mc.createServer(options)
99
const mcData = require('minecraft-data')(server.version)
1010
const loginPacket = mcData.loginPacket
11+
const nbt = require('prismarine-nbt')
12+
13+
function chatText (text) {
14+
return mcData.supportFeature('chatPacketsUseNbtComponents')
15+
? nbt.comp({ text: nbt.string(text) })
16+
: JSON.stringify({ text })
17+
}
1118

1219
server.on('playerJoin', function (client) {
1320
const addr = client.socket.remoteAddress
@@ -49,14 +56,32 @@ server.on('playerJoin', function (client) {
4956
flags: 0x00
5057
})
5158

52-
const msg = {
59+
const message = {
5360
translate: 'chat.type.announcement',
5461
with: [
5562
'Server',
5663
'Hello, world!'
5764
]
5865
}
59-
client.write('chat', { message: JSON.stringify(msg), position: 0, sender: '0' })
66+
if (mcData.supportFeature('signedChat')) {
67+
client.write('player_chat', {
68+
plainMessage: message,
69+
signedChatContent: '',
70+
unsignedChatContent: chatText(message),
71+
type: 0,
72+
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
73+
senderName: JSON.stringify({ text: 'me' }),
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: 'me' })
81+
})
82+
} else {
83+
client.write('chat', { message: JSON.stringify({ text: message }), position: 0, sender: 'me' })
84+
}
6085
})
6186

6287
server.on('error', function (error) {

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@
5151
"endian-toggle": "^0.0.0",
5252
"lodash.get": "^4.1.2",
5353
"lodash.merge": "^4.3.0",
54-
"minecraft-data": "^3.53.0",
54+
"minecraft-data": "^3.55.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-chat": "^1.10.0",
5960
"prismarine-nbt": "^2.5.0",
6061
"prismarine-realms": "^1.2.0",
6162
"protodef": "^1.8.0",

src/client/chat.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const crypto = require('crypto')
22
const concat = require('../transforms/binaryStream').concat
3+
const { processNbtMessage } = require('prismarine-chat')
34
const messageExpireTime = 420000 // 7 minutes (ms)
45

56
function isFormatted (message) {
@@ -25,6 +26,10 @@ module.exports = function (client, options) {
2526
// This stores the last n (5 or 20) messages that the player has seen, from unique players
2627
if (mcData.supportFeature('chainedChatWithHashing')) client._lastSeenMessages = new LastSeenMessages()
2728
else client._lastSeenMessages = new LastSeenMessagesWithInvalidation()
29+
// 1.20.3+ serializes chat components in either NBT or JSON. If the chat is sent as NBT, then the structure read will differ
30+
// from the normal JSON structure, so it needs to be normalized. prismarine-chat processNbtMessage will do that by default
31+
// on a fromNotch call. Since we don't call fromNotch here (done in mineflayer), we manually call processNbtMessage
32+
const processMessage = (msg) => mcData.supportFeature('chatPacketsUseNbtComponents') ? processNbtMessage(msg) : msg
2833

2934
// This stores the last 128 inbound (signed) messages for 1.19.3 chat validation
3035
client._signatureCache = new SignatureCache()
@@ -139,12 +144,11 @@ module.exports = function (client, options) {
139144

140145
client.on('profileless_chat', (packet) => {
141146
// Profileless chat is parsed as an unsigned player chat message but logged as a system message
142-
143147
client.emit('playerChat', {
144-
formattedMessage: packet.message,
148+
formattedMessage: processMessage(packet.message),
145149
type: packet.type,
146-
senderName: packet.name,
147-
targetName: packet.target,
150+
senderName: processMessage(packet.name),
151+
targetName: processMessage(packet.target),
148152
verified: false
149153
})
150154

@@ -160,7 +164,7 @@ module.exports = function (client, options) {
160164
client.on('system_chat', (packet) => {
161165
client.emit('systemChat', {
162166
positionId: packet.isActionBar ? 2 : 1,
163-
formattedMessage: packet.content
167+
formattedMessage: processMessage(packet.content)
164168
})
165169

166170
client._lastChatHistory.push({
@@ -198,11 +202,11 @@ module.exports = function (client, options) {
198202
if (verified) client._signatureCache.push(packet.signature)
199203
client.emit('playerChat', {
200204
plainMessage: packet.plainMessage,
201-
unsignedContent: packet.unsignedChatContent,
205+
unsignedContent: processMessage(packet.unsignedChatContent),
202206
type: packet.type,
203207
sender: packet.senderUuid,
204-
senderName: packet.networkName,
205-
targetName: packet.networkTargetName,
208+
senderName: processMessage(packet.networkName),
209+
targetName: processMessage(packet.networkTargetName),
206210
verified
207211
})
208212

src/datatypes/uuid.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ function nameToMcOfflineUUID (name) {
1515
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
1616
}
1717

18-
module.exports = { nameToMcOfflineUUID }
18+
function fromIntArray (arr) {
19+
const buf = Buffer.alloc(16)
20+
arr.forEach((num, index) => { buf.writeInt32BE(num, index * 4) })
21+
return buf.toString('hex')
22+
}
23+
24+
module.exports = { nameToMcOfflineUUID, fromIntArray }

src/version.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

33
module.exports = {
4-
defaultVersion: '1.20.2',
5-
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']
4+
defaultVersion: '1.20.4',
5+
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']
66
}

test/clientTest.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ for (const supportedVersion of mc.supportedVersions) {
5959
'server-port': PORT,
6060
motd: 'test1234',
6161
'max-players': 120,
62+
// 'level-type': 'flat',
6263
'use-native-transport': 'false' // java 16 throws errors without this, https://www.spigotmc.org/threads/unable-to-access-address-of-buffer.311602
6364
}, (err) => {
6465
if (err) reject(err)
@@ -165,21 +166,22 @@ for (const supportedVersion of mc.supportedVersions) {
165166
}
166167
} else {
167168
// 1.19+
168-
169-
const message = JSON.parse(data.formattedMessage || JSON.stringify({ text: data.plainMessage }))
169+
console.log('Chat Message', data)
170+
const sender = JSON.parse(data.senderName)
171+
const msgPayload = data.formattedMessage ? JSON.parse(data.formattedMessage) : data.plainMessage
172+
const plainMessage = client.parseMessage(msgPayload).toString()
170173

171174
if (chatCount === 1) {
172-
assert.strictEqual(message.text, 'hello everyone; I have logged in.')
173-
const sender = JSON.parse(data.senderName)
175+
assert.strictEqual(plainMessage, 'hello everyone; I have logged in.')
174176
assert.deepEqual(sender.clickEvent, {
175177
action: 'suggest_command',
176178
value: '/tell Player '
177179
})
178180
assert.strictEqual(sender.text, 'Player')
179181
} else if (chatCount === 2) {
180-
assert.strictEqual(message.text, 'hello')
181-
const sender = JSON.parse(data.senderName)
182-
assert.strictEqual(sender.text, 'Server')
182+
const plainSender = client.parseMessage(sender).toString()
183+
assert.strictEqual(plainMessage, 'hello')
184+
assert.strictEqual(plainSender, 'Server')
183185
wrap.removeListener('line', lineListener)
184186
client.end()
185187
done()

test/common/clientHelpers.js

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Registry = require('prismarine-registry')
12
module.exports = client => {
23
client.nextMessage = (containing) => {
34
return new Promise((resolve) => {
@@ -20,5 +21,23 @@ module.exports = client => {
2021
})
2122
}
2223

24+
client.on('login', (packet) => {
25+
client.registry ??= Registry(client.version)
26+
if (packet.dimensionCodec) {
27+
client.registry.loadDimensionCodec(packet.dimensionCodec)
28+
}
29+
})
30+
client.on('registry_data', (data) => {
31+
client.registry ??= Registry(client.version)
32+
client.registry.loadDimensionCodec(data.codec)
33+
})
34+
35+
client.on('playerJoin', () => {
36+
const ChatMessage = require('prismarine-chat')(client.registry || client.version)
37+
client.parseMessage = (comp) => {
38+
return new ChatMessage(comp)
39+
}
40+
})
41+
2342
return client
2443
}

test/packetTest.js

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ const values = {
208208
packedChunkPos: {
209209
x: 10,
210210
z: 12
211+
},
212+
particle: {
213+
particleId: 0,
214+
data: null
211215
}
212216
}
213217

0 commit comments

Comments
 (0)