Skip to content

Commit d3161ba

Browse files
authored
Compressor handling update for 1.20.60 (#479)
* support compressor in header * use mcdata features * cleanup
1 parent be6f0cd commit d3161ba

File tree

6 files changed

+88
-27
lines changed

6 files changed

+88
-27
lines changed

src/client.js

+13
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Client extends Connection {
4242
this.validateOptions()
4343
this.serializer = createSerializer(this.options.version)
4444
this.deserializer = createDeserializer(this.options.version)
45+
this._loadFeatures()
4546

4647
KeyExchange(this, null, this.options)
4748
Login(this, null, this.options)
@@ -55,6 +56,17 @@ class Client extends Connection {
5556
this.emit('connect_allowed')
5657
}
5758

59+
_loadFeatures () {
60+
try {
61+
const mcData = require('minecraft-data')('bedrock_' + this.options.version)
62+
this.features = {
63+
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
64+
}
65+
} catch (e) {
66+
throw new Error(`Unsupported version: '${this.options.version}', no data available`)
67+
}
68+
}
69+
5870
connect () {
5971
if (!this.connection) throw new Error('Connect not currently allowed') // must wait for `connect_allowed`, or use `createClient`
6072
this.on('session', this._connect)
@@ -120,6 +132,7 @@ class Client extends Connection {
120132
updateCompressorSettings (packet) {
121133
this.compressionAlgorithm = packet.compression_algorithm || 'deflate'
122134
this.compressionThreshold = packet.compression_threshold
135+
this.compressionReady = true
123136
}
124137

125138
sendLogin () {

src/connection.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class Connection extends EventEmitter {
6565
write (name, params) {
6666
this.outLog?.(name, params)
6767
if (name === 'start_game') this.updateItemPalette(params.itemstates)
68-
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
68+
const batch = new Framer(this)
6969
const packet = this.serializer.createPacketBuffer({ name, params })
7070
batch.addEncodedPacket(packet)
7171

@@ -91,7 +91,7 @@ class Connection extends EventEmitter {
9191

9292
_tick () {
9393
if (this.sendQ.length) {
94-
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
94+
const batch = new Framer(this)
9595
batch.addEncodedPackets(this.sendQ)
9696
this.sendQ = []
9797
this.sendIds = []
@@ -115,7 +115,7 @@ class Connection extends EventEmitter {
115115
*/
116116
sendBuffer (buffer, immediate = false) {
117117
if (immediate) {
118-
const batch = new Framer(this.compressionAlgorithm, this.compressionLevel, this.compressionThreshold)
118+
const batch = new Framer(this)
119119
batch.addEncodedPacket(buffer)
120120
if (this.encryptionEnabled) {
121121
this.sendEncryptedBatch(batch)
@@ -150,13 +150,11 @@ class Connection extends EventEmitter {
150150
// These are callbacks called from encryption.js
151151
onEncryptedPacket = (buf) => {
152152
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
153-
154153
this.sendMCPE(packet)
155154
}
156155

157156
onDecryptedPacket = (buf) => {
158157
const packets = Framer.getPackets(buf)
159-
160158
for (const packet of packets) {
161159
this.readPacket(packet)
162160
}
@@ -167,11 +165,13 @@ class Connection extends EventEmitter {
167165
if (this.encryptionEnabled) {
168166
this.decrypt(buffer.slice(1))
169167
} else {
170-
const packets = Framer.decode(this.compressionAlgorithm, buffer)
168+
const packets = Framer.decode(this, buffer)
171169
for (const packet of packets) {
172170
this.readPacket(packet)
173171
}
174172
}
173+
} else {
174+
throw Error('Bad packet header ' + buffer[0])
175175
}
176176
}
177177
}

src/server.js

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Server extends EventEmitter {
1515

1616
this.RakServer = require('./rak')(this.options.raknetBackend).RakServer
1717

18+
this._loadFeatures(this.options.version)
1819
this.serializer = createSerializer(this.options.version)
1920
this.deserializer = createDeserializer(this.options.version)
2021
this.advertisement = new ServerAdvertisement(this.options.motd, this.options.port, this.options.version)
@@ -27,6 +28,17 @@ class Server extends EventEmitter {
2728
this.setCompressor(this.options.compressionAlgorithm, this.options.compressionLevel, this.options.compressionThreshold)
2829
}
2930

31+
_loadFeatures (version) {
32+
try {
33+
const mcData = require('minecraft-data')('bedrock_' + version)
34+
this.features = {
35+
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
36+
}
37+
} catch (e) {
38+
throw new Error(`Unsupported version: '${version}', no data available`)
39+
}
40+
}
41+
3042
setCompressor (algorithm, level = 1, threshold = 256) {
3143
switch (algorithm) {
3244
case 'none':

src/serverPlayer.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Player extends Connection {
1010
constructor (server, connection) {
1111
super()
1212
this.server = server
13+
this.features = server.features
1314
this.serializer = server.serializer
1415
this.deserializer = server.deserializer
1516
this.connection = connection
@@ -23,8 +24,8 @@ class Player extends Connection {
2324
this.status = ClientStatus.Authenticating
2425

2526
if (isDebug) {
26-
this.inLog = (...args) => debug('S ->', ...args)
27-
this.outLog = (...args) => debug('S <-', ...args)
27+
this.inLog = (...args) => debug('-> S', ...args)
28+
this.outLog = (...args) => debug('<- S', ...args)
2829
}
2930

3031
// Compression is server-wide
@@ -48,6 +49,7 @@ class Player extends Connection {
4849
client_throttle_scalar: 0
4950
})
5051
this._sentNetworkSettings = true
52+
this.compressionReady = true
5153
}
5254

5355
handleClientProtocolVersion (clientVersion) {
@@ -152,7 +154,7 @@ class Player extends Connection {
152154
return
153155
}
154156

155-
this.inLog?.(des.data.name, serialize(des.data.params).slice(0, 200))
157+
this.inLog?.(des.data.name, serialize(des.data.params))
156158

157159
switch (des.data.name) {
158160
// This is the first packet on 1.19.30 & above

src/transforms/encryption.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ function createEncryptor (client, iv) {
3636
// The send counter is represented as a little-endian 64-bit long and incremented after each packet.
3737

3838
function process (chunk) {
39-
const buffer = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
39+
const compressed = Zlib.deflateRawSync(chunk, { level: client.compressionLevel })
40+
const buffer = client.features.compressorInHeader
41+
? Buffer.concat([Buffer.from([0]), compressed])
42+
: compressed
4043
const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)])
4144
client.sendCounter++
4245
client.cipher.write(packet)
@@ -70,7 +73,22 @@ function createDecryptor (client, iv) {
7073
return
7174
}
7275

73-
const buffer = Zlib.inflateRawSync(chunk, { chunkSize: 512000 })
76+
let buffer
77+
if (client.features.compressorInHeader) {
78+
switch (packet[0]) {
79+
case 0:
80+
buffer = Zlib.inflateRawSync(packet.slice(1), { chunkSize: 512000 })
81+
break
82+
case 255:
83+
buffer = packet.slice(1)
84+
break
85+
default:
86+
client.emit('error', Error(`Unsupported compressor: ${packet[0]}`))
87+
}
88+
} else {
89+
buffer = Zlib.inflateRawSync(packet, { chunkSize: 512000 })
90+
}
91+
7492
client.onDecryptedPacket(buffer)
7593
}
7694

src/transforms/framer.js

+32-16
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ const zlib = require('zlib')
33

44
// Concatenates packets into one batch packet, and adds length prefixs.
55
class Framer {
6-
constructor (compressor, compressionLevel, compressionThreshold) {
6+
constructor (client) {
77
// Encoding
88
this.packets = []
9-
this.compressor = compressor || 'none'
10-
this.compressionLevel = compressionLevel
11-
this.compressionThreshold = compressionThreshold
9+
this.compressor = client.compressionAlgorithm || 'none'
10+
this.compressionLevel = client.compressionLevel
11+
this.compressionThreshold = client.compressionThreshold
12+
this.writeCompressor = client.features.compressorInHeader && client.compressionReady
1213
}
1314

1415
// No compression in base class
@@ -21,30 +22,45 @@ class Framer {
2122
}
2223

2324
static decompress (algorithm, buffer) {
24-
try {
25-
switch (algorithm) {
26-
case 'deflate': return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
27-
case 'snappy': throw Error('Snappy compression not implemented')
28-
case 'none': return buffer
29-
default: throw Error('Unknown compression type ' + this.compressor)
30-
}
31-
} catch {
32-
return buffer
25+
switch (algorithm) {
26+
case 0:
27+
case 'deflate':
28+
return zlib.inflateRawSync(buffer, { chunkSize: 512000 })
29+
case 1:
30+
case 'snappy':
31+
throw Error('Snappy compression not implemented')
32+
case 'none':
33+
case 255:
34+
return buffer
35+
default: throw Error('Unknown compression type ' + algorithm)
3336
}
3437
}
3538

36-
static decode (compressor, buf) {
39+
static decode (client, buf) {
3740
// Read header
3841
if (buf[0] !== 0xfe) throw Error('bad batch packet header ' + buf[0])
3942
const buffer = buf.slice(1)
40-
const decompressed = this.decompress(compressor, buffer)
43+
// Decompress
44+
let decompressed
45+
if (client.features.compressorInHeader && client.compressionReady) {
46+
decompressed = this.decompress(buffer[0], buffer.slice(1))
47+
} else {
48+
// On old versions, compressor is session-wide ; failing to decompress
49+
// a packet will assume it's not compressed
50+
try {
51+
decompressed = this.decompress(client.compressionAlgorithm, buffer)
52+
} catch (e) {
53+
decompressed = buffer
54+
}
55+
}
4156
return Framer.getPackets(decompressed)
4257
}
4358

4459
encode () {
4560
const buf = Buffer.concat(this.packets)
4661
const compressed = (buf.length > this.compressionThreshold) ? this.compress(buf) : buf
47-
return Buffer.concat([Buffer.from([0xfe]), compressed])
62+
const header = this.writeCompressor ? [0xfe, 0] : [0xfe]
63+
return Buffer.concat([Buffer.from(header), compressed])
4864
}
4965

5066
addEncodedPacket (chunk) {

0 commit comments

Comments
 (0)