Skip to content

Commit 9b029e8

Browse files
extremeheatrom1504
andauthored
1.20.5 (#1309)
* 1.20.5 * update examples * Update for 1.20.5 chat_command_signed with seperateSignedChatCommandPacket feature * updates * update java * re-enable packet tests * Update client.js add debug code after decompress * Update client.js * Update ci.yml * Add `arrayWithLengthOffset` type to interpeter * Update minecraft.js * Update compiler-minecraft.js * Update minecraft.js * lint * remote custom ci install * Update package.json * Update packetTest.js add Slot, SlotComponent * Update packetTest.js * Update packetTest.js * Fix lint. * Fix declare_recipes, Slot * Update package.json --------- Co-authored-by: Romain Beaumont <[email protected]>
1 parent 7057ad9 commit 9b029e8

File tree

17 files changed

+230
-44
lines changed

17 files changed

+230
-44
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
- name: Setup Java JDK
5353
uses: actions/[email protected]
5454
with:
55-
java-version: '17'
55+
java-version: '21'
5656
distribution: 'adopt'
5757
- name: Install dependencies
5858
run: npm install

docs/README.md

+2-1
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, 1.20.3 and 1.20.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, 1.20.2, 1.20.3, 1.20.4, 1.20.5
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.
@@ -142,6 +142,7 @@ server.on('playerJoin', function(client) {
142142

143143
client.write('login', {
144144
...loginPacket,
145+
enforceSecureChat: false,
145146
entityId: client.id,
146147
hashedSeed: [0, 0],
147148
maxPlayers: server.maxPlayers,

examples/server/server.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ server.on('playerJoin', function (client) {
3030
// send init data so client will start rendering world
3131
client.write('login', {
3232
...loginPacket,
33+
enforceSecureChat: false,
3334
entityId: client.id,
3435
isHardcore: false,
3536
gameMode: 0,

examples/server_channel/server_channel.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ server.on('playerJoin', function (client) {
1414

1515
client.write('login', {
1616
...loginPacket,
17+
enforceSecureChat: false,
1718
entityId: client.id,
1819
isHardcore: false,
1920
gameMode: 0,

examples/server_custom_channel/server_custom_channel.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const loginPacket = mcData.loginPacket
1111
server.on('playerJoin', function (client) {
1212
client.write('login', {
1313
...loginPacket,
14+
enforceSecureChat: false,
1415
entityId: client.id,
1516
isHardcore: false,
1617
gameMode: 0,

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"minecraft-wrap": "^1.2.3",
4242
"mocha": "^10.0.0",
4343
"power-assert": "^1.0.0",
44-
"standard": "^17.0.0"
44+
"standard": "^17.0.0",
45+
"prismarine-registry": "^1.8.0"
4546
},
4647
"dependencies": {
4748
"@types/readable-stream": "^4.0.0",
@@ -51,15 +52,15 @@
5152
"endian-toggle": "^0.0.0",
5253
"lodash.get": "^4.1.2",
5354
"lodash.merge": "^4.3.0",
54-
"minecraft-data": "^3.55.0",
55+
"minecraft-data": "^3.71.0",
5556
"minecraft-folder-path": "^1.2.0",
5657
"node-fetch": "^2.6.1",
5758
"node-rsa": "^0.4.2",
5859
"prismarine-auth": "^2.2.0",
5960
"prismarine-chat": "^1.10.0",
6061
"prismarine-nbt": "^2.5.0",
6162
"prismarine-realms": "^1.2.0",
62-
"protodef": "^1.8.0",
63+
"protodef": "^1.17.0",
6364
"readable-stream": "^4.1.0",
6465
"uuid-1345": "^1.0.1",
6566
"yggdrasil": "^1.4.0"

src/client.js

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class Client extends EventEmitter {
137137
this.splitter.pipe(this.deserializer)
138138
} else {
139139
this.serializer.pipe(this.compressor)
140+
if (globalThis.debugNMP) this.decompressor.on('data', (data) => { console.log('DES>', data.toString('hex')) })
140141
this.decompressor.pipe(this.deserializer)
141142
}
142143

src/client/chat.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ module.exports = function (client, options) {
176176
})
177177
})
178178

179-
client.on('message_header', (packet) => {
179+
client.on('message_header', (packet) => { // [1.19.2]
180180
updateAndValidateChat(packet.senderUuid, packet.previousSignature, packet.signature, packet.messageHash)
181181

182182
client._lastChatHistory.push({
@@ -369,13 +369,14 @@ module.exports = function (client, options) {
369369

370370
if (message.startsWith('/')) {
371371
const command = message.slice(1)
372-
if (mcData.supportFeature('useChatSessions')) {
372+
if (mcData.supportFeature('useChatSessions')) { // 1.19.3+
373373
const { acknowledged, acknowledgements } = getAcknowledgements()
374-
client.write('chat_command', {
374+
const canSign = client.profileKeys && client._session
375+
client.write((mcData.supportFeature('seperateSignedChatCommandPacket') && canSign) ? 'chat_command_signed' : 'chat_command', {
375376
command,
376377
timestamp: options.timestamp,
377378
salt: options.salt,
378-
argumentSignatures: (client.profileKeys && client._session) ? signaturesForCommand(command, options.timestamp, options.salt, options.preview, acknowledgements) : [],
379+
argumentSignatures: canSign ? signaturesForCommand(command, options.timestamp, options.salt, options.preview, acknowledgements) : [],
379380
messageCount: client._lastSeenMessages.pending,
380381
acknowledged
381382
})

src/client/play.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ module.exports = function (client, options) {
77
client.on('server_data', (packet) => {
88
client.serverFeatures = {
99
chatPreview: packet.previewsChat,
10-
enforcesSecureChat: packet.enforcesSecureChat
10+
enforcesSecureChat: packet.enforcesSecureChat // in LoginPacket v>=1.20.5
1111
}
1212
})
1313

14-
client.once('login', () => {
14+
client.once('login', (packet) => {
15+
if (packet.enforcesSecureChat) client.serverFeatures.enforcesSecureChat = packet.enforcesSecureChat
1516
const mcData = require('minecraft-data')(client.version)
1617
if (mcData.supportFeature('useChatSessions') && client.profileKeys && client.cipher && client.session.selectedProfile.id === client.uuid.replace(/-/g, '')) {
1718
client._session = {
@@ -52,6 +53,9 @@ module.exports = function (client, options) {
5253
client.write('configuration_acknowledged', {})
5354
}
5455
client.state = states.CONFIGURATION
56+
client.on('select_known_packs', () => {
57+
client.write('select_known_packs', { packs: [] })
58+
})
5559
// Server should send finish_configuration on its own right after sending the client a dimension codec
5660
// for login (that has data about world height, world gen, etc) after getting a login success from client
5761
client.once('finish_configuration', () => {

src/datatypes/compiler-minecraft.js

+53
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ module.exports = {
4040
code += ' if ((item & 128) === 0) return { value: data, size: cursor - offset }\n'
4141
code += '}'
4242
return compiler.wrapCode(code)
43+
}],
44+
arrayWithLengthOffset: ['parametrizable', (compiler, array) => {
45+
let code = ''
46+
if (array.countType) {
47+
code += 'const { value: count, size: countSize } = ' + compiler.callType(array.countType) + '\n'
48+
} else if (array.count) {
49+
code += 'const count = ' + array.count + '\n'
50+
code += 'const countSize = 0\n'
51+
} else {
52+
throw new Error('Array must contain either count or countType')
53+
}
54+
code += 'if (count > 0xffffff) throw new Error("array size is abnormally large, not reading: " + count)\n'
55+
code += 'const data = []\n'
56+
code += 'let size = countSize\n'
57+
code += `for (let i = 0; i < count + ${array.lengthOffset}; i++) {\n`
58+
code += ' const elem = ' + compiler.callType(array.type, 'offset + size') + '\n'
59+
code += ' data.push(elem.value)\n'
60+
code += ' size += elem.size\n'
61+
code += '}\n'
62+
code += 'return { value: data, size }'
63+
return compiler.wrapCode(code)
4364
}]
4465
},
4566
Write: {
@@ -72,6 +93,19 @@ module.exports = {
7293
code += '}\n'
7394
code += 'return offset'
7495
return compiler.wrapCode(code)
96+
}],
97+
arrayWithLengthOffset: ['parametrizable', (compiler, array) => {
98+
let code = ''
99+
if (array.countType) {
100+
code += 'offset = ' + compiler.callType('value.length', array.countType) + '\n'
101+
} else if (array.count === null) {
102+
throw new Error('Array must contain either count or countType')
103+
}
104+
code += 'for (let i = 0; i < value.length; i++) {\n'
105+
code += ' offset = ' + compiler.callType('value[i]', array.type) + '\n'
106+
code += '}\n'
107+
code += 'return offset'
108+
return compiler.wrapCode(code)
75109
}]
76110
},
77111
SizeOf: {
@@ -96,6 +130,25 @@ module.exports = {
96130
code += '}\n'
97131
code += 'return size'
98132
return compiler.wrapCode(code)
133+
}],
134+
arrayWithLengthOffset: ['parametrizable', (compiler, array) => {
135+
let code = ''
136+
if (array.countType) {
137+
code += 'let size = ' + compiler.callType('value.length', array.countType) + '\n'
138+
} else if (array.count) {
139+
code += 'let size = 0\n'
140+
} else {
141+
throw new Error('Array must contain either count or countType')
142+
}
143+
if (!isNaN(compiler.callType('value[i]', array.type))) {
144+
code += 'size += value.length * ' + compiler.callType('value[i]', array.type) + '\n'
145+
} else {
146+
code += 'for (let i = 0; i < value.length; i++) {\n'
147+
code += ' size += ' + compiler.callType('value[i]', array.type) + '\n'
148+
code += '}\n'
149+
}
150+
code += 'return size'
151+
return compiler.wrapCode(code)
99152
}]
100153
}
101154
}

src/datatypes/minecraft.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ module.exports = {
1111
compressedNbt: [readCompressedNbt, writeCompressedNbt, sizeOfCompressedNbt],
1212
restBuffer: [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
1313
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
14-
topBitSetTerminatedArray: [readTopBitSetTerminatedArray, writeTopBitSetTerminatedArray, sizeOfTopBitSetTerminatedArray]
14+
topBitSetTerminatedArray: [readTopBitSetTerminatedArray, writeTopBitSetTerminatedArray, sizeOfTopBitSetTerminatedArray],
15+
arrayWithLengthOffset: [readArrayWithLengthOffset, writeArrayWithLengthOffset, sizeOfArrayWithLengthOffset]
1516
}
1617
const PartialReadError = require('protodef').utils.PartialReadError
1718

@@ -180,3 +181,36 @@ function sizeOfTopBitSetTerminatedArray (value, { type }) {
180181
}
181182
return size
182183
}
184+
185+
//
186+
const { getCount, sendCount, calcCount, tryDoc } = require('protodef/src/utils')
187+
188+
function readArrayWithLengthOffset (buffer, offset, typeArgs, rootNode) {
189+
const results = {
190+
value: [],
191+
size: 0
192+
}
193+
let value
194+
let { count, size } = getCount.call(this, buffer, offset, typeArgs, rootNode)
195+
offset += size
196+
results.size += size
197+
for (let i = 0; i < count + typeArgs.lengthOffset; i++) {
198+
({ size, value } = tryDoc(() => this.read(buffer, offset, typeArgs.type, rootNode), i))
199+
results.size += size
200+
offset += size
201+
results.value.push(value)
202+
}
203+
return results
204+
}
205+
206+
// no changes
207+
function writeArrayWithLengthOffset (value, buffer, offset, typeArgs, rootNode) {
208+
offset = sendCount.call(this, value.length, buffer, offset, typeArgs, rootNode)
209+
return value.reduce((offset, v, index) => tryDoc(() => this.write(v, buffer, offset, typeArgs.type, rootNode), index), offset)
210+
}
211+
212+
function sizeOfArrayWithLengthOffset (value, typeArgs, rootNode) {
213+
let size = calcCount.call(this, value.length, typeArgs, rootNode)
214+
size = value.reduce((size, v, index) => tryDoc(() => size + this.sizeOf(v, typeArgs.type, rootNode), index), size)
215+
return size + typeArgs
216+
}

src/server/login.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ module.exports = function (client, server, options) {
193193
if (client.supportFeature('chainedChatWithHashing')) { // 1.19.1+
194194
client.write('server_data', {
195195
previewsChat: options.enableChatPreview,
196-
enforceSecureProfile: options.enforceSecureProfile
196+
// Note: in 1.20.5+ user must send this with `login`
197+
enforcesSecureChat: options.enforceSecureProfile
197198
})
198199
}
199200

@@ -211,7 +212,14 @@ module.exports = function (client, server, options) {
211212

212213
function onClientLoginAck () {
213214
client.state = states.CONFIGURATION
214-
client.write('registry_data', { codec: options.registryCodec || {} })
215+
if (client.supportFeature('segmentedRegistryCodecData')) {
216+
for (const key in options.registryCodec) {
217+
const entry = options.registryCodec[key]
218+
client.write('registry_data', entry)
219+
}
220+
} else {
221+
client.write('registry_data', { codec: options.registryCodec || {} })
222+
}
215223
client.once('finish_configuration', () => {
216224
client.state = states.PLAY
217225
server.emit('playerJoin', client)

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.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']
4+
defaultVersion: '1.20.5',
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', '1.20.5']
66
}

test/clientTest.js

+41-16
Original file line numberDiff line numberDiff line change
@@ -107,37 +107,62 @@ for (const supportedVersion of mc.supportedVersions) {
107107
auth: 'offline'
108108
}))
109109
client.on('error', err => done(err))
110-
const lineListener = function (line) {
111-
const match = line.match(/\[Server thread\/INFO\]: (?:\[Not Secure\] )?<(.+?)> (.+)/)
112-
if (!match) return
113-
assert.strictEqual(match[1], 'Player')
114-
assert.strictEqual(match[2], 'hello everyone; I have logged in.')
115-
wrap.writeServer('say hello\n')
116-
wrap.off('line', lineListener)
117-
}
118-
wrap.on('line', lineListener)
119-
let chatCount = 0
120-
client.on('login', function (packet) {
121-
assert.strictEqual(packet.gameMode, 0)
122-
client.chat('hello everyone; I have logged in.')
110+
111+
client.on('state', (state) => {
112+
console.log('Client now in state', state)
123113
})
124-
// Dump some data for easier debugging
114+
115+
// ** Dump some server data **
116+
fs.rmSync(MC_SERVER_DIR + '_registry_data.json', { force: true })
125117
client.on('raw.registry_data', (buffer) => {
126118
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.bin', buffer)
127119
})
128120
client.on('registry_data', (json) => {
129-
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.json', JSON.stringify(json))
121+
if (json.codec) { // Pre 1.20.5, codec is 1 json
122+
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.json', JSON.stringify(json))
123+
} else { // 1.20.5+, codec is many nbt's each with their own ids, merge them
124+
let currentData = {}
125+
if (fs.existsSync(MC_SERVER_DIR + '_registry_data.json')) {
126+
currentData = JSON.parse(fs.readFileSync(MC_SERVER_DIR + '_registry_data.json', 'utf8'))
127+
}
128+
currentData[json.id] = json
129+
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.json', JSON.stringify(currentData))
130+
}
131+
console.log('Wrote registry data')
130132
})
131133
client.on('login', (packet) => {
132134
fs.writeFileSync(MC_SERVER_DIR + '_login.json', JSON.stringify(packet))
133135
if (fs.existsSync(MC_SERVER_DIR + '_registry_data.json')) {
134136
// generate a loginPacket.json for minecraft-data
137+
const codec = JSON.parse(fs.readFileSync(MC_SERVER_DIR + '_registry_data.json'))
135138
fs.writeFileSync(MC_SERVER_DIR + '_loginPacket.json', JSON.stringify({
136139
...packet,
137-
dimensionCodec: JSON.parse(fs.readFileSync(MC_SERVER_DIR + '_registry_data.json')).codec
140+
dimensionCodec: codec.codec || codec
138141
}, null, 2))
142+
console.log('Wrote loginPacket.json')
139143
}
140144
})
145+
// ** End dumping code **
146+
147+
const lineListener = function (line) {
148+
const match = line.match(/\[Server thread\/INFO\]: (?:\[Not Secure\] )?<(.+?)> (.+)/)
149+
if (!match) return
150+
assert.strictEqual(match[1], 'Player')
151+
assert.strictEqual(match[2], 'hello everyone; I have logged in.')
152+
wrap.writeServer('say hello\n')
153+
wrap.off('line', lineListener)
154+
}
155+
wrap.on('line', lineListener)
156+
let chatCount = 0
157+
158+
client.on('login', function (packet) {
159+
if (packet.worldState) { // 1.20.5+
160+
assert.strictEqual(packet.worldState.gamemode, 'survival')
161+
} else {
162+
assert.strictEqual(packet.gameMode, 0)
163+
}
164+
client.chat('hello everyone; I have logged in.')
165+
})
141166
client.on('playerChat', function (data) {
142167
chatCount += 1
143168
assert.ok(chatCount <= 2)

test/common/clientHelpers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = client => {
2929
})
3030
client.on('registry_data', (data) => {
3131
client.registry ??= Registry(client.version)
32-
client.registry.loadDimensionCodec(data.codec)
32+
client.registry.loadDimensionCodec(data.codec || data)
3333
})
3434

3535
client.on('playerJoin', () => {

0 commit comments

Comments
 (0)