diff --git a/index.js b/index.js index 5e117d7..a63a88c 100644 --- a/index.js +++ b/index.js @@ -141,6 +141,8 @@ function createServer(options) { hash = crypto.createHash("sha1"); hash.update(serverId); client.once([states.LOGIN, 0x01], onEncryptionKeyResponse); + client.once([states.LOGIN, 0x03], onCompressionRequest); + client.on( [states.PLAY, 0x46], onCompressionRequest); client.write(0x01, { serverId: serverId, publicKey: client.publicKey, @@ -193,6 +195,10 @@ function createServer(options) { } } + function onCompressionRequest(packet) { + client.compressionThreshold = packet.threshold; + } + function loginClient() { var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; if (onlineMode == false || isException) { diff --git a/lib/client.js b/lib/client.js index 23d5576..c9f20dd 100644 --- a/lib/client.js +++ b/lib/client.js @@ -4,6 +4,7 @@ var net = require('net') , protocol = require('./protocol') , dns = require('dns') , createPacketBuffer = protocol.createPacketBuffer + , compressPacketBuffer = protocol.compressPacketBuffer , parsePacket = protocol.parsePacket , states = protocol.states , debug = protocol.debug @@ -30,6 +31,7 @@ function Client(isServer) { this.encryptionEnabled = false; this.cipher = null; this.decipher = null; + this.compressionThreshold = -1; this.packetsToParse = {}; this.on('newListener', function(event, listener) { var direction = this.isServer ? 'toServer' : 'toClient'; @@ -153,18 +155,38 @@ Client.prototype.write = function(packetId, params) { packetId = packetId[1]; } + // TODO: Which comes first, encryption or compression? + + var finishWriting = function(buffer) { + debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")"); + debug(params); + var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; + this.socket.write(out); + return true; + } + var buffer = createPacketBuffer(packetId, this.state, params, this.isServer); - debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")"); - debug(params); - var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; - this.socket.write(out); - return true; + if(this.compressionThreshold != -1 && buffer.length > this.compressionThreshold) { + compressPacketBuffer(buffer, finishWriting); + } else { + finishWriting(buffer); + } + }; Client.prototype.writeRaw = function(buffer, shouldEncrypt) { if (shouldEncrypt === null) { shouldEncrypt = true; } - var out = (shouldEncrypt && this.encryptionEnabled) ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; - this.socket.write(out); + + var finishWriting = function(buffer) { + var out = (shouldEncrypt && this.encryptionEnabled) ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; + this.socket.write(out); + }; + + if(this.compressionThreshold != -1 && buffer.length > this.compressionThreshold) { + compressPacketBuffer(buffer, finishWriting); + } else { + finishWriting(buffer); + } }; diff --git a/lib/protocol.js b/lib/protocol.js index c9c0538..902ceca 100644 --- a/lib/protocol.js +++ b/lib/protocol.js @@ -1,5 +1,6 @@ var assert = require('assert'); var util = require('util'); +var zlib = require('zlib'); var STRING_MAX_LENGTH = 240; var SRV_STRING_MAX_LENGTH = 32767; @@ -58,6 +59,9 @@ var packets = { success: {id: 0x02, fields: [ { name: "uuid", type: "string" }, { name: "username", type: "string" } + ]}, + compress: { id: 0x03, fields: [ + { name: "threshold", type: "varint"} ]} }, toServer: { @@ -1408,6 +1412,19 @@ function createPacketBuffer(packetId, state, params, isServer) { return buffer; } +function compressPacketBuffer(buffer, callback) { + var dataLength = buffer.length; + var packetLength = dataLength + sizeOfVarInt(dataLength); + var data = zlib.deflateRaw(buffer, function(compressedBuffer) { + var size = sizeOfVarInt(packetLength) + sizeOfVarInt(packetLength) + compressedBuffer.length; + var packetBuffer = new Buffer(size); + var offset = writeVarInt(packetLength, packetBuffer, 0); + offset = writeVarInt(dataLength, packetBuffer, offset); + writeVarInt(compressedBuffer, packetBuffer, offset); + callback(packetBuffer); + }); +} + function parsePacket(buffer, state, isServer, packetsToParse) { if (state == null) state = states.PLAY; var cursor = 0; @@ -1484,6 +1501,7 @@ module.exports = { sessionVersion: 13, parsePacket: parsePacket, createPacketBuffer: createPacketBuffer, + compressPacketBuffer: compressPacketBuffer, STRING_MAX_LENGTH: STRING_MAX_LENGTH, packetIds: packetIds, packetNames: packetNames,