From c24718f64cdae262c90961b2ccfd3065f2150905 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 14:45:41 -0800 Subject: [PATCH 1/7] Recognize legacy ping in splitter. GH-332 --- src/transforms/framing.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/transforms/framing.js b/src/transforms/framing.js index a4fb0f7..a447137 100644 --- a/src/transforms/framing.js +++ b/src/transforms/framing.js @@ -30,6 +30,13 @@ class Splitter extends Transform { } _transform(chunk, enc, cb) { this.buffer = Buffer.concat([this.buffer, chunk]); + + if (this.buffer[0] === 0xfe) { + // legacy_server_list_ping packet follows a different protocol format, no varint length + this.push(this.buffer); + return cb(); + } + var offset = 0; var { value, size, error } = readVarInt(this.buffer, offset) || { error: "Not enough data" }; From 4840ffe7b14cc0640683024421ed88dec58aaafa Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 15:20:53 -0800 Subject: [PATCH 2/7] Splitter prepends length header to legacy ping for deserializer support --- src/transforms/framing.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/transforms/framing.js b/src/transforms/framing.js index a447137..38d7eb4 100644 --- a/src/transforms/framing.js +++ b/src/transforms/framing.js @@ -23,6 +23,8 @@ class Framer extends Transform { } } +const LEGACY_PING_PACKET_ID = 0xfe; + class Splitter extends Transform { constructor() { super(); @@ -31,9 +33,15 @@ class Splitter extends Transform { _transform(chunk, enc, cb) { this.buffer = Buffer.concat([this.buffer, chunk]); - if (this.buffer[0] === 0xfe) { - // legacy_server_list_ping packet follows a different protocol format, no varint length - this.push(this.buffer); + // TODO: only decode if in handshake state! important since 254 is a valid varint (encodes as 0xfe 0x01), packet length + if (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(); } From 9b824b5b888c1e5c25bcba754feb4b55d72c894d Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 15:28:40 -0800 Subject: [PATCH 3/7] Legacy ping is only allowed when in HANDSHAKING state (otherwise, varint packet length 254) --- src/client.js | 3 ++- src/transforms/framing.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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/transforms/framing.js b/src/transforms/framing.js index 38d7eb4..e2ff26c 100644 --- a/src/transforms/framing.js +++ b/src/transforms/framing.js @@ -29,12 +29,12 @@ 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]); - // TODO: only decode if in handshake state! important since 254 is a valid varint (encodes as 0xfe 0x01), packet length - if (this.buffer[0] === LEGACY_PING_PACKET_ID) { + 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)); From af542c29f2747be9fef92fcb8bf21495c2f8a3d1 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 15:56:42 -0800 Subject: [PATCH 4/7] Add server support for legacy ping type 0 (0xfe) --- src/createServer.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/createServer.js b/src/createServer.js index 5b56fdd..c86dd03 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -40,6 +40,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 +127,31 @@ function createServer(options) { }); } + function onLegacyPing(packet) { + console.log('onLegacyPing',packet); + if (packet.payload === 0) { + var responseString = [server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\xa7'); + + function utf16be(s) { + //var responseBuffer = new Buffer(responseString, 'utf16le'); // unfortunately, we need utf16be not le + //var responseBuffer = new Buffer(responseString, 'ucs2'); // aliases for utf16le + // hack semi-UTF16BE encoding, by prefixing each character with a null byte + // TODO: use a real encoder, maybe https://github.com/ForbesLindesay/legacy-encoding? + // uses https://github.com/ashtuchkin/iconv-lite which has 'utf16-be'. use or separate out? + return new Buffer([''].concat(s.split('')).join('\0'), 'binary'); + } + + var responseBuffer = utf16be(responseString); + + var length = responseString.length; // UCS2 characters, not bytes + var lengthBuffer = new Buffer(2); + lengthBuffer.writeUInt16BE(length); + + client.writeRaw(Buffer.concat([new Buffer('ff', 'hex'), lengthBuffer, responseBuffer])); + } + // TODO: support ping type 1 + } + function onLogin(packet) { client.username = packet.username; var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; From dda0cb2e77ac9811ded20f11a264f3fe62d69dc1 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 16:00:12 -0800 Subject: [PATCH 5/7] Refactor ping replying into sendPingResponse() --- src/createServer.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index c86dd03..6b315f3 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -129,9 +129,15 @@ function createServer(options) { function onLegacyPing(packet) { console.log('onLegacyPing',packet); - if (packet.payload === 0) { - var responseString = [server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\xa7'); + if (packet.payload === 1) { + // TODO: ping type 1 + } else { + // ping type 0 + sendPingResponse([server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\xa7')); + } + + function sendPingResponse(responseString) { function utf16be(s) { //var responseBuffer = new Buffer(responseString, 'utf16le'); // unfortunately, we need utf16be not le //var responseBuffer = new Buffer(responseString, 'ucs2'); // aliases for utf16le @@ -149,7 +155,7 @@ function createServer(options) { client.writeRaw(Buffer.concat([new Buffer('ff', 'hex'), lengthBuffer, responseBuffer])); } - // TODO: support ping type 1 + } function onLogin(packet) { From 6eb95766cba7d49c29750d06405f8d09bf01d07d Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 16:07:56 -0800 Subject: [PATCH 6/7] Add legacy ping type 1 support (includes versions) --- src/createServer.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 6b315f3..6dd9d88 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -128,10 +128,10 @@ function createServer(options) { } function onLegacyPing(packet) { - console.log('onLegacyPing',packet); - if (packet.payload === 1) { - // TODO: ping type 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')); @@ -153,7 +153,10 @@ function createServer(options) { var lengthBuffer = new Buffer(2); lengthBuffer.writeUInt16BE(length); - client.writeRaw(Buffer.concat([new Buffer('ff', 'hex'), lengthBuffer, responseBuffer])); + var raw = Buffer.concat([new Buffer('ff', 'hex'), lengthBuffer, responseBuffer]); + + //client.writeRaw(raw); // not raw enough, it includes length + client.socket.write(raw); } } From 4a677a25bad5fdf14b559237c1151454de323d50 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 30 Jan 2016 21:40:03 -0800 Subject: [PATCH 7/7] Use endian-toggle for UTF-16BE encoding (not in nodejs, see https://github.com/nodejs/node-v0.x-archive/issues/1684) --- package.json | 1 + src/createServer.js | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) 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/createServer.js b/src/createServer.js index 6dd9d88..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; @@ -139,12 +140,7 @@ function createServer(options) { function sendPingResponse(responseString) { function utf16be(s) { - //var responseBuffer = new Buffer(responseString, 'utf16le'); // unfortunately, we need utf16be not le - //var responseBuffer = new Buffer(responseString, 'ucs2'); // aliases for utf16le - // hack semi-UTF16BE encoding, by prefixing each character with a null byte - // TODO: use a real encoder, maybe https://github.com/ForbesLindesay/legacy-encoding? - // uses https://github.com/ashtuchkin/iconv-lite which has 'utf16-be'. use or separate out? - return new Buffer([''].concat(s.split('')).join('\0'), 'binary'); + return endianToggle(new Buffer(s, 'utf16le'), 16); } var responseBuffer = utf16be(responseString);