Compressor handling update for 1.20.60 (#479)
* support compressor in header * use mcdata features * cleanup
This commit is contained in:
parent
be6f0cde9f
commit
d3161badc6
6 changed files with 88 additions and 27 deletions
|
|
@ -42,6 +42,7 @@ class Client extends Connection {
|
|||
this.validateOptions()
|
||||
this.serializer = createSerializer(this.options.version)
|
||||
this.deserializer = createDeserializer(this.options.version)
|
||||
this._loadFeatures()
|
||||
|
||||
KeyExchange(this, null, this.options)
|
||||
Login(this, null, this.options)
|
||||
|
|
@ -55,6 +56,17 @@ class Client extends Connection {
|
|||
this.emit('connect_allowed')
|
||||
}
|
||||
|
||||
_loadFeatures () {
|
||||
try {
|
||||
const mcData = require('minecraft-data')('bedrock_' + this.options.version)
|
||||
this.features = {
|
||||
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Unsupported version: '${this.options.version}', no data available`)
|
||||
}
|
||||
}
|
||||
|
||||
connect () {
|
||||
if (!this.connection) throw new Error('Connect not currently allowed') // must wait for `connect_allowed`, or use `createClient`
|
||||
this.on('session', this._connect)
|
||||
|
|
@ -120,6 +132,7 @@ class Client extends Connection {
|
|||
updateCompressorSettings (packet) {
|
||||
this.compressionAlgorithm = packet.compression_algorithm || 'deflate'
|
||||
this.compressionThreshold = packet.compression_threshold
|
||||
this.compressionReady = true
|
||||
}
|
||||
|
||||
sendLogin () {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class Connection extends EventEmitter {
|
|||
write (name, params) {
|
||||
this.outLog?.(name, params)
|
||||
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
const batch = new Framer(this)
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
batch.addEncodedPacket(packet)
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ class Connection extends EventEmitter {
|
|||
|
||||
_tick () {
|
||||
if (this.sendQ.length) {
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
const batch = new Framer(this)
|
||||
batch.addEncodedPackets(this.sendQ)
|
||||
this.sendQ = []
|
||||
this.sendIds = []
|
||||
|
|
@ -115,7 +115,7 @@ class Connection extends EventEmitter {
|
|||
*/
|
||||
sendBuffer (buffer, immediate = false) {
|
||||
if (immediate) {
|
||||
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
|
||||
const batch = new Framer(this)
|
||||
batch.addEncodedPacket(buffer)
|
||||
if (this.encryptionEnabled) {
|
||||
this.sendEncryptedBatch(batch)
|
||||
|
|
@ -150,13 +150,11 @@ class Connection extends EventEmitter {
|
|||
// These are callbacks called from encryption.js
|
||||
onEncryptedPacket = (buf) => {
|
||||
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
|
||||
|
||||
this.sendMCPE(packet)
|
||||
}
|
||||
|
||||
onDecryptedPacket = (buf) => {
|
||||
const packets = Framer.getPackets(buf)
|
||||
|
||||
for (const packet of packets) {
|
||||
this.readPacket(packet)
|
||||
}
|
||||
|
|
@ -167,11 +165,13 @@ class Connection extends EventEmitter {
|
|||
if (this.encryptionEnabled) {
|
||||
this.decrypt(buffer.slice(1))
|
||||
} else {
|
||||
const packets = Framer.decode(this.compressionAlgorithm, buffer)
|
||||
const packets = Framer.decode(this, buffer)
|
||||
for (const packet of packets) {
|
||||
this.readPacket(packet)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Error('Bad packet header ' + buffer[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class Server extends EventEmitter {
|
|||
|
||||
this.RakServer = require('./rak')(this.options.raknetBackend).RakServer
|
||||
|
||||
this._loadFeatures(this.options.version)
|
||||
this.serializer = createSerializer(this.options.version)
|
||||
this.deserializer = createDeserializer(this.options.version)
|
||||
this.advertisement = new ServerAdvertisement(this.options.motd, this.options.port, this.options.version)
|
||||
|
|
@ -27,6 +28,17 @@ class Server extends EventEmitter {
|
|||
this.setCompressor(this.options.compressionAlgorithm, this.options.compressionLevel, this.options.compressionThreshold)
|
||||
}
|
||||
|
||||
_loadFeatures (version) {
|
||||
try {
|
||||
const mcData = require('minecraft-data')('bedrock_' + version)
|
||||
this.features = {
|
||||
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Unsupported version: '${version}', no data available`)
|
||||
}
|
||||
}
|
||||
|
||||
setCompressor (algorithm, level = 1, threshold = 256) {
|
||||
switch (algorithm) {
|
||||
case 'none':
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class Player extends Connection {
|
|||
constructor (server, connection) {
|
||||
super()
|
||||
this.server = server
|
||||
this.features = server.features
|
||||
this.serializer = server.serializer
|
||||
this.deserializer = server.deserializer
|
||||
this.connection = connection
|
||||
|
|
@ -23,8 +24,8 @@ class Player extends Connection {
|
|||
this.status = ClientStatus.Authenticating
|
||||
|
||||
if (isDebug) {
|
||||
this.inLog = (...args) => debug('S ->', ...args)
|
||||
this.outLog = (...args) => debug('S <-', ...args)
|
||||
this.inLog = (...args) => debug('-> S', ...args)
|
||||
this.outLog = (...args) => debug('<- S', ...args)
|
||||
}
|
||||
|
||||
// Compression is server-wide
|
||||
|
|
@ -48,6 +49,7 @@ class Player extends Connection {
|
|||
client_throttle_scalar: 0
|
||||
})
|
||||
this._sentNetworkSettings = true
|
||||
this.compressionReady = true
|
||||
}
|
||||
|
||||
handleClientProtocolVersion (clientVersion) {
|
||||
|
|
@ -152,7 +154,7 @@ class Player extends Connection {
|
|||
return
|
||||
}
|
||||
|
||||
this.inLog?.(des.data.name, serialize(des.data.params).slice(0, 200))
|
||||
this.inLog?.(des.data.name, serialize(des.data.params))
|
||||
|
||||
switch (des.data.name) {
|
||||
// This is the first packet on 1.19.30 & above
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ function createEncryptor (client, iv) {
|
|||
// The send counter is represented as a little-endian 64-bit long and incremented after each packet.
|
||||
|
||||
function process (chunk) {
|
||||
const buffer = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
|
||||
const compressed = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
|
||||
const buffer = client.features.compressorInHeader
|
||||
? Buffer.concat([Buffer.from([0]), compressed])
|
||||
: compressed
|
||||
const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)])
|
||||
client.sendCounter++
|
||||
client.cipher.write(packet)
|
||||
|
|
@ -70,7 +73,22 @@ function createDecryptor (client, iv) {
|
|||
return
|
||||
}
|
||||
|
||||
const buffer = Zlib.inflateRawSync(chunk, { chunkSize: 512000 })
|
||||
let buffer
|
||||
if (client.features.compressorInHeader) {
|
||||
switch (packet[0]) {
|
||||
case 0:
|
||||
buffer = Zlib.inflateRawSync(packet.slice(1), { chunkSize: 512000 })
|
||||
break
|
||||
case 255:
|
||||
buffer = packet.slice(1)
|
||||
break
|
||||
default:
|
||||
client.emit('error', Error(`Unsupported compressor: ${packet[0]}`))
|
||||
}
|
||||
} else {
|
||||
buffer = Zlib.inflateRawSync(packet, { chunkSize: 512000 })
|
||||
}
|
||||
|
||||
client.onDecryptedPacket(buffer)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ const zlib = require('zlib')
|
|||
|
||||
// Concatenates packets into one batch packet, and adds length prefixs.
|
||||
class Framer {
|
||||
constructor (compressor, compressionLevel, compressionThreshold) {
|
||||
constructor (client) {
|
||||
// Encoding
|
||||
this.packets = []
|
||||
this.compressor = compressor || 'none'
|
||||
this.compressionLevel = compressionLevel
|
||||
this.compressionThreshold = compressionThreshold
|
||||
this.compressor = client.compressionAlgorithm || 'none'
|
||||
this.compressionLevel = client.compressionLevel
|
||||
this.compressionThreshold = client.compressionThreshold
|
||||
this.writeCompressor = client.features.compressorInHeader && client.compressionReady
|
||||
}
|
||||
|
||||
// No compression in base class
|
||||
|
|
@ -21,30 +22,45 @@ class Framer {
|
|||
}
|
||||
|
||||
static decompress (algorithm, buffer) {
|
||||
try {
|
||||
switch (algorithm) {
|
||||
case 'deflate': return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
|
||||
case 'snappy': throw Error('Snappy compression not implemented')
|
||||
case 'none': return buffer
|
||||
default: throw Error('Unknown compression type ' + this.compressor)
|
||||
}
|
||||
} catch {
|
||||
return buffer
|
||||
switch (algorithm) {
|
||||
case 0:
|
||||
case 'deflate':
|
||||
return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
|
||||
case 1:
|
||||
case 'snappy':
|
||||
throw Error('Snappy compression not implemented')
|
||||
case 'none':
|
||||
case 255:
|
||||
return buffer
|
||||
default: throw Error('Unknown compression type ' + algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
static decode (compressor, buf) {
|
||||
static decode (client, buf) {
|
||||
// Read header
|
||||
if (buf[0] !== 0xfe) throw Error('bad batch packet header ' + buf[0])
|
||||
const buffer = buf.slice(1)
|
||||
const decompressed = this.decompress(compressor, buffer)
|
||||
// Decompress
|
||||
let decompressed
|
||||
if (client.features.compressorInHeader && client.compressionReady) {
|
||||
decompressed = this.decompress(buffer[0], buffer.slice(1))
|
||||
} else {
|
||||
// On old versions, compressor is session-wide ; failing to decompress
|
||||
// a packet will assume it's not compressed
|
||||
try {
|
||||
decompressed = this.decompress(client.compressionAlgorithm, buffer)
|
||||
} catch (e) {
|
||||
decompressed = buffer
|
||||
}
|
||||
}
|
||||
return Framer.getPackets(decompressed)
|
||||
}
|
||||
|
||||
encode () {
|
||||
const buf = Buffer.concat(this.packets)
|
||||
const compressed = (buf.length > this.compressionThreshold) ? this.compress(buf) : buf
|
||||
return Buffer.concat([Buffer.from([0xfe]), compressed])
|
||||
const header = this.writeCompressor ? [0xfe, 0] : [0xfe]
|
||||
return Buffer.concat([Buffer.from(header), compressed])
|
||||
}
|
||||
|
||||
addEncodedPacket (chunk) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue