* Rename 'session' (packet) to chat_session_update to fix auth event conflict

* impl packet "bundle" grouping, add client.writeBundle(packets)

* fix handling, test

* test 1.19.4

* 1.19.4 test ci

* test ci against mcdata fork

* lint

* fix delim

* fix 1.19.3 being skipped

* Update ci.yml

* Update package.json

---------

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
This commit is contained in:
extremeheat 2023-06-03 15:54:31 -04:00 committed by GitHub
parent a32a1cf478
commit 2718bc64c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 28 deletions

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
mcVersion: ['1.7', '1.8', '1.9', '1.10', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3'] mcVersion: ['1.7', '1.8', '1.9', '1.10', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4']
fail-fast: false fail-fast: false
steps: steps:

View File

@ -1,2 +1,2 @@
tasks: tasks:
- command: npm install - command: npm install && sdk install java

View File

@ -51,7 +51,7 @@
"endian-toggle": "^0.0.0", "endian-toggle": "^0.0.0",
"lodash.get": "^4.1.2", "lodash.get": "^4.1.2",
"lodash.merge": "^4.3.0", "lodash.merge": "^4.3.0",
"minecraft-data": "^3.21.0", "minecraft-data": "^3.34.0",
"minecraft-folder-path": "^1.2.0", "minecraft-folder-path": "^1.2.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-rsa": "^0.4.2", "node-rsa": "^0.4.2",

View File

@ -1,5 +1,4 @@
'use strict' 'use strict'
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter
const debug = require('debug')('minecraft-protocol') const debug = require('debug')('minecraft-protocol')
const compression = require('./transforms/compression') const compression = require('./transforms/compression')
@ -30,8 +29,9 @@ class Client extends EventEmitter {
this.latency = 0 this.latency = 0
this.hideErrors = hideErrors this.hideErrors = hideErrors
this.closeTimer = null this.closeTimer = null
const mcData = require('minecraft-data')(version)
this.state = states.HANDSHAKING this.state = states.HANDSHAKING
this._hasBundlePacket = mcData.supportFeature('hasBundlePacket')
} }
get state () { get state () {
@ -77,7 +77,13 @@ class Client extends EventEmitter {
if (!this.compressor) { this.splitter.pipe(this.deserializer) } else { this.decompressor.pipe(this.deserializer) } if (!this.compressor) { this.splitter.pipe(this.deserializer) } else { this.decompressor.pipe(this.deserializer) }
this.emit('error', e) this.emit('error', e)
}) })
this._mcBundle = []
const emitPacket = (parsed) => {
this.emit('packet', parsed.data, parsed.metadata, parsed.buffer, parsed.fullBuffer)
this.emit(parsed.metadata.name, parsed.data, parsed.metadata)
this.emit('raw.' + parsed.metadata.name, parsed.buffer, parsed.metadata)
this.emit('raw', parsed.buffer, parsed.metadata)
}
this.deserializer.on('data', (parsed) => { this.deserializer.on('data', (parsed) => {
parsed.metadata.name = parsed.data.name parsed.metadata.name = parsed.data.name
parsed.data = parsed.data.params parsed.data = parsed.data.params
@ -87,10 +93,18 @@ class Client extends EventEmitter {
const s = JSON.stringify(parsed.data, null, 2) const s = JSON.stringify(parsed.data, null, 2)
debug(s && s.length > 10000 ? parsed.data : s) debug(s && s.length > 10000 ? parsed.data : s)
} }
this.emit('packet', parsed.data, parsed.metadata, parsed.buffer, parsed.fullBuffer) if (parsed.metadata.name === 'bundle_delimiter') {
this.emit(parsed.metadata.name, parsed.data, parsed.metadata) if (this._mcBundle.length) {
this.emit('raw.' + parsed.metadata.name, parsed.buffer, parsed.metadata) this._mcBundle.forEach(emitPacket)
this.emit('raw', parsed.buffer, parsed.metadata) this._mcBundle = []
} else { // Start bundle
this._mcBundle.push(parsed)
}
} else if (this._mcBundle.length) {
this._mcBundle.push(parsed)
} else {
emitPacket(parsed)
}
}) })
} }
@ -221,6 +235,12 @@ class Client extends EventEmitter {
this.serializer.write({ name, params }) this.serializer.write({ name, params })
} }
writeBundle (packets) {
if (this._hasBundlePacket) this.write('bundle_delimiter', {})
for (const [name, params] of packets) this.write(name, params)
if (this._hasBundlePacket) this.write('bundle_delimiter', {})
}
writeRaw (buffer) { writeRaw (buffer) {
const stream = this.compressor === null ? this.framer : this.compressor const stream = this.compressor === null ? this.framer : this.compressor
if (!stream.writable) { return } if (!stream.writable) { return }

View File

@ -19,7 +19,7 @@ module.exports = function (client, options) {
uuid: uuid.v4fast() uuid: uuid.v4fast()
} }
client.write('session', { client.write('chat_session_update', {
sessionUUID: client._session.uuid, sessionUUID: client._session.uuid,
expireTime: client.profileKeys ? BigInt(client.profileKeys.expiresOn.getTime()) : undefined, expireTime: client.profileKeys ? BigInt(client.profileKeys.expiresOn.getTime()) : undefined,
publicKey: client.profileKeys ? client.profileKeys.public.export({ type: 'spki', format: 'der' }) : undefined, publicKey: client.profileKeys ? client.profileKeys.public.export({ type: 'spki', format: 'der' }) : undefined,

View File

@ -76,7 +76,7 @@ module.exports = function (client, server, options) {
} }
} }
client.on('session', (packet) => { client.on('chat_session_update', (packet) => {
client._session = { client._session = {
index: 0, index: 0,
uuid: packet.sessionUuid uuid: packet.sessionUuid

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
module.exports = { module.exports = {
defaultVersion: '1.19.3', defaultVersion: '1.19.4',
supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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'] supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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']
} }

View File

@ -82,7 +82,7 @@ const values = {
} }
Object.keys(typeArgs).forEach(function (index) { Object.keys(typeArgs).forEach(function (index) {
const v = typeArgs[index].name === 'type' && typeArgs[index].type === 'string' && typeArgs[2] !== undefined && const v = typeArgs[index].name === 'type' && typeArgs[index].type === 'string' && typeArgs[2] !== undefined &&
typeArgs[2].type !== undefined typeArgs[2].type !== undefined
? (typeArgs[2].type[1].fields['minecraft:crafting_shapeless'] === undefined ? 'crafting_shapeless' : 'minecraft:crafting_shapeless') ? (typeArgs[2].type[1].fields['minecraft:crafting_shapeless'] === undefined ? 'crafting_shapeless' : 'minecraft:crafting_shapeless')
: getValue(typeArgs[index].type, results) : getValue(typeArgs[index].type, results)
if (typeArgs[index].anon) { if (typeArgs[index].anon) {
@ -96,6 +96,15 @@ const values = {
delete results['..'] delete results['..']
return results return results
}, },
vec3f: {
x: 0, y: 0, z: 0
},
vec3f64: {
x: 0, y: 0, z: 0
},
vec4f: {
x: 0, y: 0, z: 0, w: 0
},
count: 1, // TODO : might want to set this to a correct value count: 1, // TODO : might want to set this to a correct value
bool: true, bool: true,
f64: 99999.2222, f64: 99999.2222,
@ -266,8 +275,7 @@ for (const supportedVersion of mc.supportedVersions) {
Object.keys(packets[state]).forEach(function (direction) { Object.keys(packets[state]).forEach(function (direction) {
Object.keys(packets[state][direction].types) Object.keys(packets[state][direction].types)
.filter(function (packetName) { .filter(function (packetName) {
return packetName !== 'packet' && return packetName !== 'packet' && packetName.startsWith('packet_')
packetName.startsWith('packet_')
}) })
.forEach(function (packetName) { .forEach(function (packetName) {
packetInfo = packets[state][direction].types[packetName] packetInfo = packets[state][direction].types[packetName]

View File

@ -62,23 +62,23 @@ for (const supportedVersion of mc.supportedVersions) {
// removed `dimension` // removed `dimension`
// removed `dimensionCodec` // removed `dimensionCodec`
registryCodec: { registryCodec: {
"type": "compound", type: 'compound',
"name": "", name: '',
"value": {} value: {}
}, },
worldType: "minecraft:overworld", worldType: 'minecraft:overworld',
death: undefined death: undefined
// more to be added // more to be added
} }
} }
function sendBroadcastMessage(server, clients, message, sender) { function sendBroadcastMessage (server, clients, message, sender) {
if (mcData.supportFeature('signedChat')) { if (mcData.supportFeature('signedChat')) {
server.writeToClients(clients, 'player_chat', { server.writeToClients(clients, 'player_chat', {
plainMessage: message, plainMessage: message,
signedChatContent: '', signedChatContent: '',
unsignedChatContent: JSON.stringify({ text: message }), unsignedChatContent: JSON.stringify({ text: message }),
type: 0, type: 0,
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
senderName: JSON.stringify({ text: sender }), senderName: JSON.stringify({ text: sender }),
senderTeam: undefined, senderTeam: undefined,
@ -96,7 +96,7 @@ for (const supportedVersion of mc.supportedVersions) {
describe('mc-server ' + version.minecraftVersion, function () { describe('mc-server ' + version.minecraftVersion, function () {
this.timeout(5000) this.timeout(5000)
this.beforeAll(async function() { this.beforeAll(async function () {
PORT = await getPort() PORT = await getPort()
console.log(`Using port for tests: ${PORT}`) console.log(`Using port for tests: ${PORT}`)
}) })
@ -214,7 +214,7 @@ for (const supportedVersion of mc.supportedVersions) {
sample: [] sample: []
}, },
description: { description: {
extra: [ { color: 'red', text: 'Red text' } ], extra: [{ color: 'red', text: 'Red text' }],
bold: true, bold: true,
text: 'Example chat mesasge' text: 'Example chat mesasge'
} }
@ -278,7 +278,7 @@ for (const supportedVersion of mc.supportedVersions) {
version: version.minecraftVersion, version: version.minecraftVersion,
port: PORT port: PORT
}) })
client.on('packet', (data, {name})=>{ client.on('packet', (data, { name }) => {
if (name === 'success') { if (name === 'success') {
assert.strictEqual(data.uuid, notchUUID, 'UUID') assert.strictEqual(data.uuid, notchUUID, 'UUID')
server.close() server.close()
@ -333,7 +333,7 @@ for (const supportedVersion of mc.supportedVersions) {
})) }))
const p1Join = await player1.nextMessage('player2') const p1Join = await player1.nextMessage('player2')
assert.strictEqual(p1Join, '{"text":"player2 joined the game."}') assert.strictEqual(p1Join, '{"text":"player2 joined the game."}')
player2.chat('hi') player2.chat('hi')
@ -441,7 +441,7 @@ for (const supportedVersion of mc.supportedVersions) {
sendBroadcastMessage(server, Object.values(server.clients), 'A message from the server.') sendBroadcastMessage(server, Object.values(server.clients), 'A message from the server.')
let results = await Promise.all([player1.nextMessage(), player2.nextMessage()]) const results = await Promise.all([player1.nextMessage(), player2.nextMessage()])
for (const msg of results) { for (const msg of results) {
assert.strictEqual(msg, '{"text":"A message from the server."}') assert.strictEqual(msg, '{"text":"A message from the server."}')
} }
@ -452,5 +452,43 @@ for (const supportedVersion of mc.supportedVersions) {
server.close() server.close()
}) })
}) })
it('supports bundle packet', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
server.on('login', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'ServerShutdown')
})
client.write('login', loginPacket(client, server))
client.writeBundle([
['update_time', { age: 1, time: 2 }],
['close_window', { windowId: 0 }]
])
})
server.on('close', done)
server.on('listening', function () {
const client = mc.createClient({
username: 'lalalal',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
})
client.on('update_time', function () {
// Below handler synchronously defined should be guaranteed to be called after the above one
const d1 = Date.now()
client.on('close_window', function () {
server.close()
const d2 = Date.now()
if (mcData.supportFeature('hasBundlePacket') && (d2 - d1) > 1) {
throw new Error(`bundle packet constituents did not arrive at once : ${d1}, ${d2}`)
}
})
})
})
})
}) })
} }