Skip to content

Commit 7207b61

Browse files
extremeheatrom1504
andauthored
Fixes to protocol Holder implementation (#1355)
* Continued updates * bail tests early on failure * improve deserialization errors to include packet headers * Update play.js * Update server.js * Update ci.yml * add debug logging * update chat player_info handling * Fix 1.7 player_info and chat type field * cleanup * Update compiler-minecraft.js - important fix * Update compiler-minecraft.js fix holder type * Update ci.yml --------- Co-authored-by: Romain Beaumont <[email protected]>
1 parent 1e38d8f commit 7207b61

File tree

11 files changed

+81
-193
lines changed

11 files changed

+81
-193
lines changed

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ server.on('playerJoin', function(client) {
174174
plainMessage: message,
175175
signedChatContent: '',
176176
unsignedChatContent: chatText(message),
177-
type: 0,
177+
type: mcData.supportFeature('chatTypeIsHolder') ? { chatType: 1 } : 0,
178178
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
179179
senderName: JSON.stringify({ text: 'me' }),
180180
senderTeam: undefined,

examples/server/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function sendBroadcastMessage (server, clients, message, sender) {
7575
plainMessage: message,
7676
signedChatContent: '',
7777
unsignedChatContent: chatText(message),
78-
type: 0,
78+
type: mcData.supportFeature('chatTypeIsHolder') ? { chatType: 1 } : 0,
7979
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
8080
senderName: JSON.stringify({ text: sender }),
8181
senderTeam: undefined,

examples/server_helloworld/server_helloworld.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ server.on('playerJoin', function (client) {
6868
plainMessage: message,
6969
signedChatContent: '',
7070
unsignedChatContent: chatText(message),
71-
type: 0,
71+
type: mcData.supportFeature('chatTypeIsHolder') ? { chatType: 1 } : 0,
7272
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
7373
senderName: JSON.stringify({ text: 'me' }),
7474
senderTeam: undefined,

src/client.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ class Client extends EventEmitter {
6767
})
6868

6969
this.deserializer.on('error', (e) => {
70-
let parts
70+
let parts = []
7171
if (e.field) {
7272
parts = e.field.split('.')
7373
parts.shift()
74-
} else { parts = [] }
74+
}
7575
const deserializerDirection = this.isServer ? 'toServer' : 'toClient'
7676
e.field = [this.protocolState, deserializerDirection].concat(parts).join('.')
77-
e.message = `Deserialization error for ${e.field} : ${e.message}`
77+
e.message = e.buffer ? `Parse error for ${e.field} (${e.buffer?.length} bytes, ${e.buffer?.toString('hex').slice(0, 6)}...) : ${e.message}` : `Parse error for ${e.field}: ${e.message}`
7878
if (!this.compressor) { this.splitter.pipe(this.deserializer) } else { this.decompressor.pipe(this.deserializer) }
7979
this.emit('error', e)
8080
})

src/client/chat.js

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -106,38 +106,29 @@ module.exports = function (client, options) {
106106
})
107107

108108
client.on('player_info', (packet) => {
109-
if (mcData.supportFeature('playerInfoActionIsBitfield')) { // 1.19.3+
110-
if (packet.action & 2) { // chat session
111-
for (const player of packet.data) {
112-
if (!player.chatSession) continue
113-
client._players[player.UUID] = {
114-
publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
115-
publicKeyDER: player.chatSession.publicKey.keyBytes,
116-
sessionUuid: player.chatSession.uuid
117-
}
118-
client._players[player.UUID].sessionIndex = true
119-
client._players[player.UUID].hasChainIntegrity = true
109+
for (const player of packet.data) {
110+
if (player.chatSession) {
111+
client._players[player.uuid] = {
112+
publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
113+
publicKeyDER: player.chatSession.publicKey.keyBytes,
114+
sessionUuid: player.chatSession.uuid
120115
}
116+
client._players[player.uuid].sessionIndex = true
117+
client._players[player.uuid].hasChainIntegrity = true
121118
}
122119

123-
return
124-
}
125-
126-
if (packet.action === 0) { // add player
127-
for (const player of packet.data) {
128-
if (player.crypto) {
129-
client._players[player.UUID] = {
130-
publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
131-
publicKeyDER: player.crypto.publicKey,
132-
signature: player.crypto.signature,
133-
displayName: player.displayName || player.name
134-
}
135-
client._players[player.UUID].hasChainIntegrity = true
120+
if (player.crypto) {
121+
client._players[player.uuid] = {
122+
publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
123+
publicKeyDER: player.crypto.publicKey,
124+
signature: player.crypto.signature,
125+
displayName: player.displayName || player.name
136126
}
127+
client._players[player.uuid].hasChainIntegrity = true
137128
}
138-
} else if (packet.action === 4) { // remove player
139-
for (const player of packet.data) {
140-
delete client._players[player.UUID]
129+
130+
if (packet.action === 'remove_player') { // Only 1.8-1.9
131+
delete client._players[player.uuid]
141132
}
142133
}
143134
})

src/client/play.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ module.exports = function (client, options) {
6666
}
6767

6868
function onReady () {
69-
client.emit('playerJoin')
7069
if (mcData.supportFeature('signedChat')) {
7170
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
7271
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
@@ -86,8 +85,8 @@ module.exports = function (client, options) {
8685
function unsignedChat (message) {
8786
client.write('chat', { message })
8887
}
89-
9088
client.chat = client._signedChat || unsignedChat
89+
client.emit('playerJoin')
9190
}
9291
}
9392
}

src/datatypes/compiler-minecraft.js

Lines changed: 8 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -42,56 +42,14 @@ module.exports = {
4242
code += '}'
4343
return compiler.wrapCode(code)
4444
}],
45-
arrayWithLengthOffset: ['parametrizable', (compiler, array) => { // TODO: remove
46-
let code = ''
47-
if (array.countType) {
48-
code += 'const { value: count, size: countSize } = ' + compiler.callType(array.countType) + '\n'
49-
} else if (array.count) {
50-
code += 'const count = ' + array.count + '\n'
51-
code += 'const countSize = 0\n'
52-
} else {
53-
throw new Error('Array must contain either count or countType')
54-
}
55-
code += 'if (count > 0xffffff) throw new Error("array size is abnormally large, not reading: " + count)\n'
56-
code += 'const data = []\n'
57-
code += 'let size = countSize\n'
58-
code += `for (let i = 0; i < count + ${array.lengthOffset}; i++) {\n`
59-
code += ' const elem = ' + compiler.callType(array.type, 'offset + size') + '\n'
60-
code += ' data.push(elem.value)\n'
61-
code += ' size += elem.size\n'
62-
code += '}\n'
63-
code += 'return { value: data, size }'
64-
return compiler.wrapCode(code)
65-
}],
66-
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
67-
let fstr = JSON.stringify(flags)
68-
if (Array.isArray(flags)) {
69-
fstr = '{'
70-
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
71-
fstr += '}'
72-
} else if (shift) {
73-
fstr = '{'
74-
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`
75-
fstr += '}'
76-
}
77-
return compiler.wrapCode(`
78-
const { value: _value, size } = ${compiler.callType(type, 'offset')}
79-
const value = { _value }
80-
const flags = ${fstr}
81-
for (const key in flags) {
82-
value[key] = (_value & flags[key]) == flags[key]
83-
}
84-
return { value, size }
85-
`.trim())
86-
}],
8745
registryEntryHolder: ['parametrizable', (compiler, opts) => {
8846
return compiler.wrapCode(`
8947
const { value: n, size: nSize } = ${compiler.callType('varint')}
9048
if (n !== 0) {
9149
return { value: { ${opts.baseName}: n - 1 }, size: nSize }
9250
} else {
93-
const holder = ${compiler.callType(opts.otherwise.type)}
94-
return { value: { ${opts.otherwise.name}: holder.data }, size: nSize + holder.size }
51+
const holder = ${compiler.callType(opts.otherwise.type, 'offset + nSize')}
52+
return { value: { ${opts.otherwise.name}: holder.value }, size: nSize + holder.size }
9553
}
9654
`.trim())
9755
}],
@@ -145,46 +103,14 @@ if (n !== 0) {
145103
code += 'return offset'
146104
return compiler.wrapCode(code)
147105
}],
148-
arrayWithLengthOffset: ['parametrizable', (compiler, array) => {
149-
let code = ''
150-
if (array.countType) {
151-
code += 'offset = ' + compiler.callType('value.length', array.countType) + '\n'
152-
} else if (array.count === null) {
153-
throw new Error('Array must contain either count or countType')
154-
}
155-
code += 'for (let i = 0; i < value.length; i++) {\n'
156-
code += ' offset = ' + compiler.callType('value[i]', array.type) + '\n'
157-
code += '}\n'
158-
code += 'return offset'
159-
return compiler.wrapCode(code)
160-
}],
161-
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
162-
let fstr = JSON.stringify(flags)
163-
if (Array.isArray(flags)) {
164-
fstr = '{'
165-
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
166-
fstr += '}'
167-
} else if (shift) {
168-
fstr = '{'
169-
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`
170-
fstr += '}'
171-
}
172-
return compiler.wrapCode(`
173-
const flags = ${fstr}
174-
let val = value._value ${big ? '|| 0n' : ''}
175-
for (const key in flags) {
176-
if (value[key]) val |= flags[key]
177-
}
178-
return (ctx.${type})(val, buffer, offset)
179-
`.trim())
180-
}],
181106
registryEntryHolder: ['parametrizable', (compiler, opts) => {
182107
const baseName = `value.${opts.baseName}`
183108
const otherwiseName = `value.${opts.otherwise.name}`
184109
return compiler.wrapCode(`
185-
if (${baseName}) {
110+
if (${baseName} != null) {
186111
offset = ${compiler.callType(`${baseName} + 1`, 'varint')}
187112
} else if (${otherwiseName}) {
113+
offset += 1
188114
offset = ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)}
189115
} else {
190116
throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set')
@@ -196,7 +122,7 @@ return offset
196122
const baseName = `value.${opts.base.name}`
197123
const otherwiseName = `value.${opts.otherwise.name}`
198124
return compiler.wrapCode(`
199-
if (${baseName}) {
125+
if (${baseName} != null) {
200126
offset = ${compiler.callType(0, 'varint')}
201127
offset = ${compiler.callType(`${baseName}`, opts.base.type)}
202128
} else if (${otherwiseName}) {
@@ -234,53 +160,15 @@ return offset
234160
code += 'return size'
235161
return compiler.wrapCode(code)
236162
}],
237-
arrayWithLengthOffset: ['parametrizable', (compiler, array) => {
238-
let code = ''
239-
if (array.countType) {
240-
code += 'let size = ' + compiler.callType('value.length', array.countType) + '\n'
241-
} else if (array.count) {
242-
code += 'let size = 0\n'
243-
} else {
244-
throw new Error('Array must contain either count or countType')
245-
}
246-
if (!isNaN(compiler.callType('value[i]', array.type))) {
247-
code += 'size += value.length * ' + compiler.callType('value[i]', array.type) + '\n'
248-
} else {
249-
code += 'for (let i = 0; i < value.length; i++) {\n'
250-
code += ' size += ' + compiler.callType('value[i]', array.type) + '\n'
251-
code += '}\n'
252-
}
253-
code += 'return size'
254-
return compiler.wrapCode(code)
255-
}],
256-
bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => {
257-
let fstr = JSON.stringify(flags)
258-
if (Array.isArray(flags)) {
259-
fstr = '{'
260-
for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',')
261-
fstr += '}'
262-
} else if (shift) {
263-
fstr = '{'
264-
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`
265-
fstr += '}'
266-
}
267-
return compiler.wrapCode(`
268-
const flags = ${fstr}
269-
let val = value._value ${big ? '|| 0n' : ''}
270-
for (const key in flags) {
271-
if (value[key]) val |= flags[key]
272-
}
273-
return (ctx.${type})(val)
274-
`.trim())
275-
}],
276163
registryEntryHolder: ['parametrizable', (compiler, opts) => {
277164
const baseName = `value.${opts.baseName}`
278165
const otherwiseName = `value.${opts.otherwise.name}`
279166
return compiler.wrapCode(`
280167
let size = 0
281-
if (${baseName}) {
168+
if (${baseName} != null) {
282169
size += ${compiler.callType(`${baseName} + 1`, 'varint')}
283170
} else if (${otherwiseName}) {
171+
size += 1
284172
size += ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)}
285173
} else {
286174
throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set')
@@ -293,7 +181,7 @@ return size
293181
const otherwiseName = `value.${opts.otherwise.name}`
294182
return compiler.wrapCode(`
295183
let size = 0
296-
if (${baseName}) {
184+
if (${baseName} != null) {
297185
size += ${compiler.callType(0, 'varint')}
298186
size += ${compiler.callType(`${baseName}`, opts.base.type)}
299187
} else if (${otherwiseName}) {

src/datatypes/minecraft.js

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

@@ -181,36 +180,3 @@ function sizeOfTopBitSetTerminatedArray (value, { type }) {
181180
}
182181
return size
183182
}
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.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class Server extends EventEmitter {
3434
: JSON.stringify({ text: endReason })
3535
client.write('kick_disconnect', { reason: fullReason })
3636
} else if (client.state === states.LOGIN) {
37-
client.write('disconnect', { reason: fullReason || endReason })
37+
fullReason ||= JSON.stringify({ text: endReason })
38+
client.write('disconnect', { reason: fullReason })
3839
}
3940
client._end(endReason)
4041
}

0 commit comments

Comments
 (0)