diff --git a/package.json b/package.json index fd3abd5..4fd7100 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "browser": "browser.js", "devDependencies": { "babel-preset-es2015": "^6.3.13", + "endian-toggle": "0.0.0", "espower-loader": "^1.0.0", "gulp": "^3.9.0", "gulp-babel": "^6.1.1", diff --git a/src/client.js b/src/client.js index 8a0a129..fd31a2c 100644 --- a/src/client.js +++ b/src/client.js @@ -14,6 +14,7 @@ class Client extends EventEmitter super(); this.version=version; this.isServer = !!isServer; + this.splitter=framing.createSplitter(); this.setSerializer(states.HANDSHAKING); this.packetsToParse={}; this.serializer; @@ -21,7 +22,6 @@ class Client extends EventEmitter this.framer=framing.createFramer(); this.cipher=null; this.decipher=null; - this.splitter=framing.createSplitter(); this.decompressor=null; this.deserializer; this.isServer; @@ -51,6 +51,7 @@ class Client extends EventEmitter this.deserializer = createDeserializer({ isServer:this.isServer, version:this.version, state: state, packetsToParse: this.packetsToParse}); + this.splitter.recognizeLegacyPing = state === states.HANDSHAKING; this.serializer.on('error', (e) => { var parts=e.field.split("."); diff --git a/src/client/tcp_dns.js b/src/client/tcp_dns.js index a8e3060..579cb80 100644 --- a/src/client/tcp_dns.js +++ b/src/client/tcp_dns.js @@ -6,7 +6,9 @@ module.exports = function(client, options) { options.host = options.host || 'localhost'; options.connect = (client) => { - if(options.port == 25565 && net.isIP(options.host) === 0) { + if (options.stream) { + client.setSocket(options.stream); + } else if (options.port == 25565 && net.isIP(options.host) === 0) { dns.resolveSrv("_minecraft._tcp." + options.host, function(err, addresses) { if(addresses && addresses.length > 0) { client.setSocket(net.connect(addresses[0].port, addresses[0].name)); diff --git a/src/createServer.js b/src/createServer.js index 5b56fdd..9a4b71d 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -5,6 +5,7 @@ var states = require("./states"); var bufferEqual = require('buffer-equal'); var Server = require('./server'); var UUID = require('uuid-1345'); +var endianToggle = require('endian-toggle'); module.exports=createServer; @@ -40,6 +41,7 @@ function createServer(options) { client.once('set_protocol', onHandshake); client.once('login_start', onLogin); client.once('ping_start', onPing); + client.once('legacy_server_list_ping', onLegacyPing); client.on('end', onEnd); var keepAlive = false; @@ -126,6 +128,35 @@ function createServer(options) { }); } + function onLegacyPing(packet) { + if (packet.payload === 1) { + var pingVersion = 1; + sendPingResponse('\xa7' + [pingVersion, version.version, version.minecraftVersion, + server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\0')); + } else { + // ping type 0 + sendPingResponse([server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\xa7')); + } + + function sendPingResponse(responseString) { + function utf16be(s) { + return endianToggle(new Buffer(s, 'utf16le'), 16); + } + + var responseBuffer = utf16be(responseString); + + var length = responseString.length; // UCS2 characters, not bytes + var lengthBuffer = new Buffer(2); + lengthBuffer.writeUInt16BE(length); + + var raw = Buffer.concat([new Buffer('ff', 'hex'), lengthBuffer, responseBuffer]); + + //client.writeRaw(raw); // not raw enough, it includes length + client.socket.write(raw); + } + + } + function onLogin(packet) { client.username = packet.username; var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; diff --git a/src/transforms/framing.js b/src/transforms/framing.js index a4fb0f7..48e67c2 100644 --- a/src/transforms/framing.js +++ b/src/transforms/framing.js @@ -15,21 +15,37 @@ class Framer extends Transform { } _transform(chunk, enc, cb) { - var buffer = new Buffer(sizeOfVarInt(chunk.length)); + const varIntSize=sizeOfVarInt(chunk.length); + var buffer = new Buffer(varIntSize + chunk.length); writeVarInt(chunk.length, buffer, 0); + chunk.copy(buffer, varIntSize); this.push(buffer); - this.push(chunk); return cb(); } } +const LEGACY_PING_PACKET_ID = 0xfe; + class Splitter extends Transform { constructor() { super(); this.buffer = new Buffer(0); + this.recognizeLegacyPing = false; } _transform(chunk, enc, cb) { this.buffer = Buffer.concat([this.buffer, chunk]); + + if (this.recognizeLegacyPing && this.buffer[0] === LEGACY_PING_PACKET_ID) { + // legacy_server_list_ping packet follows a different protocol format + // prefix the encoded varint packet id for the deserializer + var header = new Buffer(sizeOfVarInt(LEGACY_PING_PACKET_ID)); + writeVarInt(LEGACY_PING_PACKET_ID, header, 0); + var payload = this.buffer.slice(1); // remove 0xfe packet id + if (payload.length === 0) payload = new Buffer('\0'); // TODO: update minecraft-data to recognize a lone 0xfe, https://github.com/PrismarineJS/minecraft-data/issues/95 + this.push(Buffer.concat([header, payload])); + return cb(); + } + var offset = 0; var { value, size, error } = readVarInt(this.buffer, offset) || { error: "Not enough data" };