From 60c24a68b7b1c3799c012dfa3d9dfa3c4518ca94 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sun, 17 Jan 2016 23:20:03 -0800 Subject: [PATCH 01/21] Remove unused chai dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 9d23285..3346302 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ }, "browser": "browser.js", "devDependencies": { - "chai": "^2.3.0", "espower-loader": "^1.0.0", "gulp": "^3.8.11", "gulp-babel": "^5.1.0", From 91a404333ebce09c28e82bd225ece2ca88e49007 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sun, 17 Jan 2016 22:55:53 -0800 Subject: [PATCH 02/21] Remove non-ES6 class field declarations (ES7 stage 1 proposal) ES6 does not have support for class field declarations, although it is an experimental ES7 proposal: https://github.com/jeffmo/es-class-fields-and-static-properties For ES6, move the field/property declarations to the constructors. --- src/client.js | 29 ++++++++++++++--------------- src/server.js | 9 ++++----- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/client.js b/src/client.js index 40eba1f..e0b8d25 100644 --- a/src/client.js +++ b/src/client.js @@ -10,26 +10,25 @@ var createDeserializer=require("./transforms/serializer").createDeserializer; class Client extends EventEmitter { - packetsToParse={}; - serializer; - compressor=null; - framer=framing.createFramer(); - cipher=null; - decipher=null; - splitter=framing.createSplitter(); - decompressor=null; - deserializer; - isServer; - version; - protocolState=states.HANDSHAKING; - ended=true; - latency=0; - constructor(isServer,version) { super(); this.version=version; this.isServer = !!isServer; this.setSerializer(states.HANDSHAKING); + this.packetsToParse={}; + this.serializer; + this.compressor=null; + this.framer=framing.createFramer(); + this.cipher=null; + this.decipher=null; + this.splitter=framing.createSplitter(); + this.decompressor=null; + this.deserializer; + this.isServer; + this.version; + this.protocolState=states.HANDSHAKING; + this.ended=true; + this.latency=0; this.on('newListener', function(event, listener) { var direction = this.isServer ? 'toServer' : 'toClient'; diff --git a/src/server.js b/src/server.js index acc76f4..d604b9f 100644 --- a/src/server.js +++ b/src/server.js @@ -5,14 +5,13 @@ var states = require("./states"); class Server extends EventEmitter { - socketServer=null; - cipher=null; - decipher=null; - clients={}; - constructor(version) { super(); this.version=version; + this.socketServer=null; + this.cipher=null; + this.decipher=null; + this.clients={}; } listen(port, host) { From 76fca99ac4b49ae05230ec3a19cf161355933076 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 18 Jan 2016 18:17:53 -0800 Subject: [PATCH 03/21] Disable ES7 experimental stage 0 features in favor of ES6 standard --- gulpfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 6053692..ddb187a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,7 +3,6 @@ var gulp = require('gulp'); var plumber = require('gulp-plumber'); var babel = require('gulp-babel'); var options = { - stage: 0, // Dat ES7 goodness optional: ["runtime"] }; From 3f161622f9afb25c686fb27f92aa49b26569b48a Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Mon, 16 Nov 2015 16:37:28 +0100 Subject: [PATCH 04/21] update to babel6 and remove some dependencies --- gulpfile.js | 2 +- package.json | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index ddb187a..d3e62ff 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,7 +3,7 @@ var gulp = require('gulp'); var plumber = require('gulp-plumber'); var babel = require('gulp-babel'); var options = { - optional: ["runtime"] + presets: ['es2015'] }; var sourcemaps = require('gulp-sourcemaps'); diff --git a/package.json b/package.json index 3346302..fa9508a 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,12 @@ }, "browser": "browser.js", "devDependencies": { + "babel-preset-es2015": "^6.3.13", "espower-loader": "^1.0.0", - "gulp": "^3.8.11", - "gulp-babel": "^5.1.0", + "gulp": "^3.9.0", + "gulp-babel": "^6.1.1", "gulp-plumber": "^1.0.1", - "gulp-sourcemaps": "^1.3.0", + "gulp-sourcemaps": "^1.6.0", "intelli-espower-loader": "^1.0.0", "minecraft-wrap": "~0.6.5", "mocha": "~2.3.3", @@ -41,15 +42,12 @@ "source-map-support": "^0.3.2" }, "dependencies": { - "babel-runtime": "^5.4.4", "buffer-equal": "0.0.1", - "lodash.reduce": "^3.1.2", "minecraft-data": "^0.16.1", "node-uuid": "~1.4.1", "prismarine-nbt": "0.1.0", "protodef": "0.2.5", "readable-stream": "^1.1.0", - "superagent": "~0.10.0", "ursa-purejs": "0.0.3", "uuid": "^2.0.1", "uuid-1345": "^0.99.6", From 4aac85c09a142a46c70bf0d593df8daf92f3dd8a Mon Sep 17 00:00:00 2001 From: deathcap Date: Fri, 22 Jan 2016 00:32:36 -0800 Subject: [PATCH 05/21] Show disconnect reason in examples --- examples/client_chat/client_chat.js | 4 ++++ examples/client_echo/client_echo.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/examples/client_chat/client_chat.js b/examples/client_chat/client_chat.js index 3c5d66d..d70b7ef 100644 --- a/examples/client_chat/client_chat.js +++ b/examples/client_chat/client_chat.js @@ -93,6 +93,10 @@ client.on('connect', function() { console.info(color('Successfully connected to ' + host + ':' + port, "blink+green")); }); +client.on('disconnect', function(packet) { + console.log('disconnected: '+ packet.reason); +}); + client.on('end', function() { console.log("Connection lost"); process.exit(); diff --git a/examples/client_echo/client_echo.js b/examples/client_echo/client_echo.js index 0caf0f1..1f64b4a 100644 --- a/examples/client_echo/client_echo.js +++ b/examples/client_echo/client_echo.js @@ -15,6 +15,9 @@ var client = mc.createClient({ client.on('connect', function() { console.info('connected'); }); +client.on('disconnect', function(packet) { + console.log('disconnected: '+ packet.reason); +}); client.on('chat', function(packet) { var jsonMsg = JSON.parse(packet.message); if(jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') { From eefb113b4cb7918b7492619a494c3f3db1af1caf Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 22 Jan 2016 12:42:04 +0100 Subject: [PATCH 06/21] use uuid-1345 instead of 3 uuid packages, fix #297 --- package.json | 2 -- src/createClient.js | 4 ++-- src/datatypes/minecraft.js | 7 ++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index fa9508a..fd3abd5 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,10 @@ "dependencies": { "buffer-equal": "0.0.1", "minecraft-data": "^0.16.1", - "node-uuid": "~1.4.1", "prismarine-nbt": "0.1.0", "protodef": "0.2.5", "readable-stream": "^1.1.0", "ursa-purejs": "0.0.3", - "uuid": "^2.0.1", "uuid-1345": "^0.99.6", "yggdrasil": "0.1.0" }, diff --git a/src/createClient.js b/src/createClient.js index 8867249..83dc1b7 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -8,7 +8,7 @@ var yggdrasil = require('yggdrasil')({}); var yggserver = require('yggdrasil').server({}); var states = require("./states"); var debug = require("./debug"); -var uuid = require('uuid'); +var UUID = require('uuid-1345'); module.exports=createClient; @@ -31,7 +31,7 @@ function createClient(options) { assert.ok(options, "options is required"); var port = options.port || 25565; var host = options.host || 'localhost'; - var clientToken = options.clientToken || uuid.v4(); + var clientToken = options.clientToken || UUID.v4().toString(); var accessToken; assert.ok(options.username, "username is required"); diff --git a/src/datatypes/minecraft.js b/src/datatypes/minecraft.js index 2b89b46..956212f 100644 --- a/src/datatypes/minecraft.js +++ b/src/datatypes/minecraft.js @@ -1,5 +1,5 @@ var nbt = require('prismarine-nbt'); -var uuid = require('node-uuid'); +var UUID = require('uuid-1345'); module.exports = { 'UUID': [readUUID, writeUUID, 16], @@ -11,13 +11,14 @@ module.exports = { function readUUID(buffer, offset) { return { - value: uuid.unparse(buffer, offset), + value: UUID.stringify(buffer.slice(offset,16)), size: 16 }; } function writeUUID(value, buffer, offset) { - uuid.parse(value, buffer, offset); + var buf=UUID.parse(value); + buf.copy(buffer,offset); return offset + 16; } From 162a8abb34daf57e5b1acd6f88aac47de30057e6 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 23 Jan 2016 12:10:26 -0800 Subject: [PATCH 07/21] Add debug logging for received packet data --- src/client.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client.js b/src/client.js index e0b8d25..200d738 100644 --- a/src/client.js +++ b/src/client.js @@ -75,6 +75,8 @@ class Client extends EventEmitter parsed.metadata.name=parsed.data.name; parsed.data=parsed.data.params; parsed.metadata.state=state; + debug("read packet " + state + "." + parsed.metadata.name); + debug(parsed.data); this.emit('packet', parsed.data, parsed.metadata); this.emit(parsed.metadata.name, parsed.data, parsed.metadata); this.emit('raw.' + parsed.metadata.name, parsed.buffer, parsed.metadata); From cbaaeb8eb264b1d9d11f847f38b165f7a765575c Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:11:42 -0800 Subject: [PATCH 08/21] Move onEncryptionKeyRequest to client/encryption --- src/client/encrypt.js | 65 +++++++++++++++++++++++++++++++++++++++++++ src/createClient.js | 51 ++------------------------------- 2 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 src/client/encrypt.js diff --git a/src/client/encrypt.js b/src/client/encrypt.js new file mode 100644 index 0000000..872dc63 --- /dev/null +++ b/src/client/encrypt.js @@ -0,0 +1,65 @@ +var crypto = require('crypto'); +var yggserver = require('yggdrasil').server({}); +var debug = require("../debug"); + +module.exports = function(client) { + client.once('encryption_begin', onEncryptionKeyRequest); + + function onEncryptionKeyRequest(packet) { + crypto.randomBytes(16, gotSharedSecret); + + function gotSharedSecret(err, sharedSecret) { + if(err) { + debug(err); + client.emit('error', err); + client.end(); + return; + } + if(haveCredentials) { + joinServerRequest(onJoinServerResponse); + } else { + if(packet.serverId != '-') { + debug('This server appears to be an online server and you are providing no password, the authentication will probably fail'); + } + sendEncryptionKeyResponse(); + } + + function onJoinServerResponse(err) { + if(err) { + client.emit('error', err); + client.end(); + } else { + sendEncryptionKeyResponse(); + } + } + + function joinServerRequest(cb) { + yggserver.join(accessToken, client.session.selectedProfile.id, + packet.serverId, sharedSecret, packet.publicKey, cb); + } + + function sendEncryptionKeyResponse() { + var pubKey = mcPubKeyToURsa(packet.publicKey); + var encryptedSharedSecretBuffer = pubKey.encrypt(sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING); + var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING); + client.write('encryption_begin', { + sharedSecret: encryptedSharedSecretBuffer, + verifyToken: encryptedVerifyTokenBuffer + }); + client.setEncryption(sharedSecret); + } + } + } +}; + +function mcPubKeyToURsa(mcPubKeyBuffer) { + var pem = "-----BEGIN PUBLIC KEY-----\n"; + var base64PubKey = mcPubKeyBuffer.toString('base64'); + var maxLineLength = 65; + while(base64PubKey.length > 0) { + pem += base64PubKey.substring(0, maxLineLength) + "\n"; + base64PubKey = base64PubKey.substring(maxLineLength); + } + pem += "-----END PUBLIC KEY-----\n"; + return ursa.createPublicKey(pem, 'utf8'); +} diff --git a/src/createClient.js b/src/createClient.js index 83dc1b7..1bba4e3 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -3,12 +3,11 @@ var net = require('net'); var dns = require('dns'); var Client = require('./client'); var assert = require('assert'); -var crypto = require('crypto'); var yggdrasil = require('yggdrasil')({}); -var yggserver = require('yggdrasil').server({}); var states = require("./states"); var debug = require("./debug"); var UUID = require('uuid-1345'); +var encrypt = require('./client/encrypt'); module.exports=createClient; @@ -47,7 +46,7 @@ function createClient(options) { var client = new Client(false,version.majorVersion); client.on('connect', onConnect); if(keepAlive) client.on('keep_alive', onKeepAlive); - client.once('encryption_begin', onEncryptionKeyRequest); + encrypt(client); client.once('success', onLogin); client.once("compress", onCompressionRequest); client.on("set_compression", onCompressionRequest); @@ -114,52 +113,6 @@ function createClient(options) { }); } - function onEncryptionKeyRequest(packet) { - crypto.randomBytes(16, gotSharedSecret); - - function gotSharedSecret(err, sharedSecret) { - if(err) { - debug(err); - client.emit('error', err); - client.end(); - return; - } - if(haveCredentials) { - joinServerRequest(onJoinServerResponse); - } else { - if(packet.serverId != '-') { - debug('This server appears to be an online server and you are providing no password, the authentication will probably fail'); - } - sendEncryptionKeyResponse(); - } - - function onJoinServerResponse(err) { - if(err) { - client.emit('error', err); - client.end(); - } else { - sendEncryptionKeyResponse(); - } - } - - function joinServerRequest(cb) { - yggserver.join(accessToken, client.session.selectedProfile.id, - packet.serverId, sharedSecret, packet.publicKey, cb); - } - - function sendEncryptionKeyResponse() { - var pubKey = mcPubKeyToURsa(packet.publicKey); - var encryptedSharedSecretBuffer = pubKey.encrypt(sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING); - var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING); - client.write('encryption_begin', { - sharedSecret: encryptedSharedSecretBuffer, - verifyToken: encryptedVerifyTokenBuffer - }); - client.setEncryption(sharedSecret); - } - } - } - function onLogin(packet) { client.state = states.PLAY; client.uuid = packet.uuid; From a727829a98455800b8e1c0223cf0c70556916173 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:19:46 -0800 Subject: [PATCH 09/21] Move onKeepAlive to client/keepalive --- src/client/keepalive.js | 15 +++++++++++++++ src/createClient.js | 26 ++------------------------ 2 files changed, 17 insertions(+), 24 deletions(-) create mode 100644 src/client/keepalive.js diff --git a/src/client/keepalive.js b/src/client/keepalive.js new file mode 100644 index 0000000..59b9b88 --- /dev/null +++ b/src/client/keepalive.js @@ -0,0 +1,15 @@ +module.exports = function(client) { + client.on('keep_alive', onKeepAlive); + + var timeout = null; + + function onKeepAlive(packet) { + if (timeout) + clearTimeout(timeout); + timeout = setTimeout(() => client.end(), checkTimeoutInterval); + client.write('keep_alive', { + keepAliveId: packet.keepAliveId + }); + } + +}; diff --git a/src/createClient.js b/src/createClient.js index 1bba4e3..11f343c 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -8,6 +8,7 @@ var states = require("./states"); var debug = require("./debug"); var UUID = require('uuid-1345'); var encrypt = require('./client/encrypt'); +var keepalive = require('./client/keepalive'); module.exports=createClient; @@ -45,7 +46,7 @@ function createClient(options) { var client = new Client(false,version.majorVersion); client.on('connect', onConnect); - if(keepAlive) client.on('keep_alive', onKeepAlive); + if(keepAlive) keepalive(client); encrypt(client); client.once('success', onLogin); client.once("compress", onCompressionRequest); @@ -85,7 +86,6 @@ function createClient(options) { client.connect(port, host); } - var timeout = null; return client; function onConnect() { @@ -104,14 +104,6 @@ function createClient(options) { function onCompressionRequest(packet) { client.compressionThreshold = packet.threshold; } - function onKeepAlive(packet) { - if (timeout) - clearTimeout(timeout); - timeout = setTimeout(() => client.end(), checkTimeoutInterval); - client.write('keep_alive', { - keepAliveId: packet.keepAliveId - }); - } function onLogin(packet) { client.state = states.PLAY; @@ -119,17 +111,3 @@ function createClient(options) { client.username = packet.username; } } - - - -function mcPubKeyToURsa(mcPubKeyBuffer) { - var pem = "-----BEGIN PUBLIC KEY-----\n"; - var base64PubKey = mcPubKeyBuffer.toString('base64'); - var maxLineLength = 65; - while(base64PubKey.length > 0) { - pem += base64PubKey.substring(0, maxLineLength) + "\n"; - base64PubKey = base64PubKey.substring(maxLineLength); - } - pem += "-----END PUBLIC KEY-----\n"; - return ursa.createPublicKey(pem, 'utf8'); -} From 20e076ebcfce2706d3f5b9b59ff27621aeb04b8f Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:22:25 -0800 Subject: [PATCH 10/21] Move onCompressionRequest to client/compress --- src/client/compress.js | 9 +++++++++ src/createClient.js | 8 ++------ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 src/client/compress.js diff --git a/src/client/compress.js b/src/client/compress.js new file mode 100644 index 0000000..40d092f --- /dev/null +++ b/src/client/compress.js @@ -0,0 +1,9 @@ +module.exports = function(client) { + client.once("compress", onCompressionRequest); + client.on("set_compression", onCompressionRequest); + + function onCompressionRequest(packet) { + client.compressionThreshold = packet.threshold; + } + // TODO: refactor with transforms/compression.js -- enable it here +}; diff --git a/src/createClient.js b/src/createClient.js index 11f343c..031e2d0 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -9,6 +9,7 @@ var debug = require("./debug"); var UUID = require('uuid-1345'); var encrypt = require('./client/encrypt'); var keepalive = require('./client/keepalive'); +var compress = require('./client/compress'); module.exports=createClient; @@ -49,8 +50,7 @@ function createClient(options) { if(keepAlive) keepalive(client); encrypt(client); client.once('success', onLogin); - client.once("compress", onCompressionRequest); - client.on("set_compression", onCompressionRequest); + compress(client); if(haveCredentials) { // make a request to get the case-correct username before connecting. var cb = function(err, session) { @@ -101,10 +101,6 @@ function createClient(options) { }); } - function onCompressionRequest(packet) { - client.compressionThreshold = packet.threshold; - } - function onLogin(packet) { client.state = states.PLAY; client.uuid = packet.uuid; From 8c4406b4a2b8e6edfc49f1bd71b0a49592fa4d04 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:24:20 -0800 Subject: [PATCH 11/21] Move keepAlive/checkTimeoutInterval to client/keepalive --- src/client/keepalive.js | 7 ++++++- src/createClient.js | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client/keepalive.js b/src/client/keepalive.js index 59b9b88..3fc817b 100644 --- a/src/client/keepalive.js +++ b/src/client/keepalive.js @@ -1,4 +1,9 @@ -module.exports = function(client) { +module.exports = function(client, options) { + var keepAlive = options.keepAlive == null ? true : options.keepAlive; + if (!keepAlive) return; + + var checkTimeoutInterval = options.checkTimeoutInterval || 10 * 1000; + client.on('keep_alive', onKeepAlive); var timeout = null; diff --git a/src/createClient.js b/src/createClient.js index 031e2d0..5e7ac3f 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -37,8 +37,6 @@ function createClient(options) { assert.ok(options.username, "username is required"); var haveCredentials = options.password != null || (clientToken != null && options.session != null); - var keepAlive = options.keepAlive == null ? true : options.keepAlive; - var checkTimeoutInterval = options.checkTimeoutInterval || 10 * 1000; var optVersion = options.version || require("./version").defaultVersion; var mcData=require("minecraft-data")(optVersion); @@ -47,7 +45,7 @@ function createClient(options) { var client = new Client(false,version.majorVersion); client.on('connect', onConnect); - if(keepAlive) keepalive(client); + keepalive(client, options); encrypt(client); client.once('success', onLogin); compress(client); From 821227d73e68dea365bcd59d8b91a91453154675 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:28:14 -0800 Subject: [PATCH 12/21] Move making a request to get case-correct username before connecting to client/caseCorrect --- src/client/caseCorrect.js | 43 +++++++++++++++++++++++++++++++++ src/createClient.js | 50 ++++++--------------------------------- 2 files changed, 50 insertions(+), 43 deletions(-) create mode 100644 src/client/caseCorrect.js diff --git a/src/client/caseCorrect.js b/src/client/caseCorrect.js new file mode 100644 index 0000000..8b99b72 --- /dev/null +++ b/src/client/caseCorrect.js @@ -0,0 +1,43 @@ +var yggdrasil = require('yggdrasil')({}); +var UUID = require('uuid-1345'); + +module.exports = function(client, options) { + var clientToken = options.clientToken || UUID.v4().toString(); + var accessToken; + var haveCredentials = options.password != null || (clientToken != null && options.session != null); + + if(haveCredentials) { + // make a request to get the case-correct username before connecting. + var cb = function(err, session) { + if(err) { + client.emit('error', err); + } else { + client.session = session; + client.username = session.selectedProfile.name; + accessToken = session.accessToken; + client.emit('session'); + client.connect(options.port, options.host); + } + }; + + if (options.session) { + yggdrasil.validate(options.session.accessToken, function(ok) { + if (ok) + cb(null, options.session); + else + yggdrasil.refresh(options.session.accessToken, options.session.clientToken, function(err, _, data) { + cb(err, data); + }); + }); + } + else yggdrasil.auth({ + user: options.username, + pass: options.password, + token: clientToken + }, cb); + } else { + // assume the server is in offline mode and just go for it. + client.username = options.username; + client.connect(options.port, options.host); + } +}; diff --git a/src/createClient.js b/src/createClient.js index 5e7ac3f..b77401e 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -3,13 +3,13 @@ var net = require('net'); var dns = require('dns'); var Client = require('./client'); var assert = require('assert'); -var yggdrasil = require('yggdrasil')({}); var states = require("./states"); var debug = require("./debug"); -var UUID = require('uuid-1345'); + var encrypt = require('./client/encrypt'); var keepalive = require('./client/keepalive'); var compress = require('./client/compress'); +var caseCorrect = require('./client/caseCorrect'); module.exports=createClient; @@ -30,13 +30,10 @@ Client.prototype.connect = function(port, host) { function createClient(options) { assert.ok(options, "options is required"); - var port = options.port || 25565; - var host = options.host || 'localhost'; - var clientToken = options.clientToken || UUID.v4().toString(); - var accessToken; + options.port = options.port || 25565; + options.host = options.host || 'localhost'; assert.ok(options.username, "username is required"); - var haveCredentials = options.password != null || (clientToken != null && options.session != null); var optVersion = options.version || require("./version").defaultVersion; var mcData=require("minecraft-data")(optVersion); @@ -49,48 +46,15 @@ function createClient(options) { encrypt(client); client.once('success', onLogin); compress(client); - if(haveCredentials) { - // make a request to get the case-correct username before connecting. - var cb = function(err, session) { - if(err) { - client.emit('error', err); - } else { - client.session = session; - client.username = session.selectedProfile.name; - accessToken = session.accessToken; - client.emit('session'); - client.connect(port, host); - } - }; - - if (options.session) { - yggdrasil.validate(options.session.accessToken, function(ok) { - if (ok) - cb(null, options.session); - else - yggdrasil.refresh(options.session.accessToken, options.session.clientToken, function(err, _, data) { - cb(err, data); - }); - }); - } - else yggdrasil.auth({ - user: options.username, - pass: options.password, - token: clientToken - }, cb); - } else { - // assume the server is in offline mode and just go for it. - client.username = options.username; - client.connect(port, host); - } + caseCorrect(client, options); return client; function onConnect() { client.write('set_protocol', { protocolVersion: version.version, - serverHost: host, - serverPort: port, + serverHost: options.host, + serverPort: options.port, nextState: 2 }); client.state = states.LOGIN; From b37dcc4d08aa2b926bbd391d521a470ef8f9cd8c Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:33:58 -0800 Subject: [PATCH 13/21] Move onConnect/set_protocol to client/setProtocol --- src/client/setProtocol.js | 21 +++++++++++++++++++++ src/createClient.js | 20 +++++--------------- 2 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 src/client/setProtocol.js diff --git a/src/client/setProtocol.js b/src/client/setProtocol.js new file mode 100644 index 0000000..865b44e --- /dev/null +++ b/src/client/setProtocol.js @@ -0,0 +1,21 @@ + +var states = require("../states"); + +module.exports = function(client, options) { + client.on('connect', onConnect); + + function onConnect() { + client.write('set_protocol', { + protocolVersion: options.protocolVersion, + serverHost: options.host, + serverPort: options.port, + nextState: 2 + }); + client.state = states.LOGIN; + client.write('login_start', { + username: client.username + }); + } + + +} diff --git a/src/createClient.js b/src/createClient.js index b77401e..97b6bc4 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -10,6 +10,7 @@ var encrypt = require('./client/encrypt'); var keepalive = require('./client/keepalive'); var compress = require('./client/compress'); var caseCorrect = require('./client/caseCorrect'); +var setProtocol = require('./client/setProtocol'); module.exports=createClient; @@ -38,10 +39,12 @@ function createClient(options) { var optVersion = options.version || require("./version").defaultVersion; var mcData=require("minecraft-data")(optVersion); var version = mcData.version; + options.majorVersion = version.majorVersion; + options.protocolVersion = version.version; - var client = new Client(false,version.majorVersion); - client.on('connect', onConnect); + var client = new Client(false, options.majorVersion); + setProtocol(client, options); keepalive(client, options); encrypt(client); client.once('success', onLogin); @@ -50,19 +53,6 @@ function createClient(options) { return client; - function onConnect() { - client.write('set_protocol', { - protocolVersion: version.version, - serverHost: options.host, - serverPort: options.port, - nextState: 2 - }); - client.state = states.LOGIN; - client.write('login_start', { - username: client.username - }); - } - function onLogin(packet) { client.state = states.PLAY; client.uuid = packet.uuid; From 34534b2aa687d415f588114407a911b22d6dd91d Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:36:36 -0800 Subject: [PATCH 14/21] Move onLogin set state=PLAY to client/play --- src/client/play.js | 11 +++++++++++ src/createClient.js | 12 +++--------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 src/client/play.js diff --git a/src/client/play.js b/src/client/play.js new file mode 100644 index 0000000..ebc491e --- /dev/null +++ b/src/client/play.js @@ -0,0 +1,11 @@ +var states = require("../states"); + +module.exports = function(client, options) { + client.once('success', onLogin); + + function onLogin(packet) { + client.state = states.PLAY; + client.uuid = packet.uuid; + client.username = packet.username; + } +}; diff --git a/src/createClient.js b/src/createClient.js index 97b6bc4..e73bce8 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -3,7 +3,6 @@ var net = require('net'); var dns = require('dns'); var Client = require('./client'); var assert = require('assert'); -var states = require("./states"); var debug = require("./debug"); var encrypt = require('./client/encrypt'); @@ -11,6 +10,7 @@ var keepalive = require('./client/keepalive'); var compress = require('./client/compress'); var caseCorrect = require('./client/caseCorrect'); var setProtocol = require('./client/setProtocol'); +var play = require('./client/play'); module.exports=createClient; @@ -42,20 +42,14 @@ function createClient(options) { options.majorVersion = version.majorVersion; options.protocolVersion = version.version; - var client = new Client(false, options.majorVersion); + setProtocol(client, options); keepalive(client, options); encrypt(client); - client.once('success', onLogin); + play(client); compress(client); caseCorrect(client, options); return client; - - function onLogin(packet) { - client.state = states.PLAY; - client.uuid = packet.uuid; - client.username = packet.username; - } } From 9c2112802a7c03929b4f3eee0ad94f1d1ff59c48 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:40:35 -0800 Subject: [PATCH 15/21] Fix ursa imports --- src/client/encrypt.js | 1 + src/createClient.js | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/encrypt.js b/src/client/encrypt.js index 872dc63..654493f 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -1,5 +1,6 @@ var crypto = require('crypto'); var yggserver = require('yggdrasil').server({}); +var ursa=require("../ursa"); var debug = require("../debug"); module.exports = function(client) { diff --git a/src/createClient.js b/src/createClient.js index e73bce8..d610c1c 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -1,9 +1,7 @@ -var ursa=require("./ursa"); var net = require('net'); var dns = require('dns'); var Client = require('./client'); var assert = require('assert'); -var debug = require("./debug"); var encrypt = require('./client/encrypt'); var keepalive = require('./client/keepalive'); From de36de84960d664393b9da82aa59bbdfa972b08f Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:47:23 -0800 Subject: [PATCH 16/21] Break client/caseCorrect dependence on TCP/IP host/port; generalize options.connect(client) --- src/client/caseCorrect.js | 4 ++-- src/createClient.js | 29 ++++++++++++++--------------- src/ping.js | 1 + 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/client/caseCorrect.js b/src/client/caseCorrect.js index 8b99b72..d27f4b9 100644 --- a/src/client/caseCorrect.js +++ b/src/client/caseCorrect.js @@ -16,7 +16,7 @@ module.exports = function(client, options) { client.username = session.selectedProfile.name; accessToken = session.accessToken; client.emit('session'); - client.connect(options.port, options.host); + options.connect(client); } }; @@ -38,6 +38,6 @@ module.exports = function(client, options) { } else { // assume the server is in offline mode and just go for it. client.username = options.username; - client.connect(options.port, options.host); + options.connect(client); } }; diff --git a/src/createClient.js b/src/createClient.js index d610c1c..201124b 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -12,26 +12,25 @@ var play = require('./client/play'); module.exports=createClient; -Client.prototype.connect = function(port, host) { - var self = this; - if(port == 25565 && net.isIP(host) === 0) { - dns.resolveSrv("_minecraft._tcp." + host, function(err, addresses) { - if(addresses && addresses.length > 0) { - self.setSocket(net.connect(addresses[0].port, addresses[0].name)); - } else { - self.setSocket(net.connect(port, host)); - } - }); - } else { - self.setSocket(net.connect(port, host)); - } -}; - function createClient(options) { assert.ok(options, "options is required"); options.port = options.port || 25565; options.host = options.host || 'localhost'; + options.connect = (client) => { + if(options.port == 25565 && net.isIP(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)); + } else { + client.setSocket(net.connect(options.port, options.host)); + } + }); + } else { + client.setSocket(net.connect(options.port, options.host)); + } + }; + assert.ok(options.username, "username is required"); var optVersion = options.version || require("./version").defaultVersion; diff --git a/src/ping.js b/src/ping.js index cf03f2f..c61bd8f 100644 --- a/src/ping.js +++ b/src/ping.js @@ -42,5 +42,6 @@ function ping(options, cb) { client.state = states.STATUS; }); + // TODO: update client.connect(port, host); } From fe327c85e132701f9f19a50fb2c92d67fb1b6b31 Mon Sep 17 00:00:00 2001 From: deathcap Date: Mon, 25 Jan 2016 23:57:10 -0800 Subject: [PATCH 17/21] Move net.socket() and dns.resolveSrv() into client/net_tcp This has been the trickest to refactor/remodularize, since it used to modify the Client prototype to augument connect(host, port). This added a dependence on TCP/IP host/port socketpairs, refactor to add a 'connect' option instead, called with the client instance. Update ping accordingly, but it has to call connect itself since it does not include client/caseCorrect, which normally calls connect. Still may be able to improve further and untangle these interdependencies cleanlier. --- src/client/tcp_dns.js | 21 +++++++++++++++++++++ src/createClient.js | 21 ++------------------- src/ping.js | 20 ++++++++++++-------- 3 files changed, 35 insertions(+), 27 deletions(-) create mode 100644 src/client/tcp_dns.js diff --git a/src/client/tcp_dns.js b/src/client/tcp_dns.js new file mode 100644 index 0000000..4ef5696 --- /dev/null +++ b/src/client/tcp_dns.js @@ -0,0 +1,21 @@ +var net = require('net'); +var dns = require('dns'); + +module.exports = function(client, options) { + options.port = options.port || 25565; + options.host = options.host || 'localhost'; + + options.connect = (client) => { + if(options.port == 25565 && net.isIP(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)); + } else { + client.setSocket(net.connect(options.port, options.host)); + } + }); + } else { + client.setSocket(net.connect(options.port, options.host)); + } + }; +}; diff --git a/src/createClient.js b/src/createClient.js index 201124b..528e417 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -1,5 +1,3 @@ -var net = require('net'); -var dns = require('dns'); var Client = require('./client'); var assert = require('assert'); @@ -9,28 +7,12 @@ var compress = require('./client/compress'); var caseCorrect = require('./client/caseCorrect'); var setProtocol = require('./client/setProtocol'); var play = require('./client/play'); +var tcp_dns = require('./client/tcp_dns'); module.exports=createClient; function createClient(options) { assert.ok(options, "options is required"); - options.port = options.port || 25565; - options.host = options.host || 'localhost'; - - options.connect = (client) => { - if(options.port == 25565 && net.isIP(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)); - } else { - client.setSocket(net.connect(options.port, options.host)); - } - }); - } else { - client.setSocket(net.connect(options.port, options.host)); - } - }; - assert.ok(options.username, "username is required"); var optVersion = options.version || require("./version").defaultVersion; @@ -41,6 +23,7 @@ function createClient(options) { var client = new Client(false, options.majorVersion); + tcp_dns(client, options); setProtocol(client, options); keepalive(client, options); encrypt(client); diff --git a/src/ping.js b/src/ping.js index c61bd8f..4a58353 100644 --- a/src/ping.js +++ b/src/ping.js @@ -1,17 +1,20 @@ var net = require('net'); var Client = require('./client'); var states = require("./states"); +var tcp_dns = require('./client/tcp_dns'); module.exports = ping; function ping(options, cb) { - var host = options.host || 'localhost'; - var port = options.port || 25565; + options.host = options.host || 'localhost'; + options.port = options.port || 25565; var optVersion = options.version || require("./version").defaultVersion; var mcData=require("minecraft-data")(optVersion); var version = mcData.version; + options.majorVersion = version.majorVersion; + options.protocolVersion = version.version; - var client = new Client(false,version.majorVersion); + var client = new Client(false,options.majorVersion); client.on('error', function(err) { cb(err); }); @@ -32,16 +35,17 @@ function ping(options, cb) { client.write('ping_start', {}); }); + // TODO: refactor with src/client/setProtocol.js client.on('connect', function() { client.write('set_protocol', { - protocolVersion: version.version, - serverHost: host, - serverPort: port, + protocolVersion: options.protocolVersion, + serverHost: options.host, + serverPort: options.port, nextState: 1 }); client.state = states.STATUS; }); - // TODO: update - client.connect(port, host); + tcp_dns(client, options); + options.connect(client); } From cf5f8c1c4c0516beac60337b6feba99183d5e443 Mon Sep 17 00:00:00 2001 From: deathcap Date: Tue, 26 Jan 2016 00:01:09 -0800 Subject: [PATCH 18/21] Pass options to all src/client/*, for consistency --- src/client/compress.js | 2 +- src/client/encrypt.js | 2 +- src/createClient.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/compress.js b/src/client/compress.js index 40d092f..cebe34f 100644 --- a/src/client/compress.js +++ b/src/client/compress.js @@ -1,4 +1,4 @@ -module.exports = function(client) { +module.exports = function(client, options) { client.once("compress", onCompressionRequest); client.on("set_compression", onCompressionRequest); diff --git a/src/client/encrypt.js b/src/client/encrypt.js index 654493f..c5c4443 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -3,7 +3,7 @@ var yggserver = require('yggdrasil').server({}); var ursa=require("../ursa"); var debug = require("../debug"); -module.exports = function(client) { +module.exports = function(client, options) { client.once('encryption_begin', onEncryptionKeyRequest); function onEncryptionKeyRequest(packet) { diff --git a/src/createClient.js b/src/createClient.js index 528e417..40a3d13 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -26,9 +26,9 @@ function createClient(options) { tcp_dns(client, options); setProtocol(client, options); keepalive(client, options); - encrypt(client); - play(client); - compress(client); + encrypt(client, options); + play(client, options); + compress(client, options); caseCorrect(client, options); return client; From 2f60088fdf90b3b5f547f287e848aecc03b7bcf3 Mon Sep 17 00:00:00 2001 From: deathcap Date: Tue, 26 Jan 2016 00:47:21 -0800 Subject: [PATCH 19/21] Fix missing tcp_dns host option --- src/client/tcp_dns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/tcp_dns.js b/src/client/tcp_dns.js index 4ef5696..a8e3060 100644 --- a/src/client/tcp_dns.js +++ b/src/client/tcp_dns.js @@ -6,7 +6,7 @@ module.exports = function(client, options) { options.host = options.host || 'localhost'; options.connect = (client) => { - if(options.port == 25565 && net.isIP(host) === 0) { + 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)); From bf5ce9e56937b53f0a9e6a9b03a51c8552d066f1 Mon Sep 17 00:00:00 2001 From: deathcap Date: Tue, 26 Jan 2016 00:51:44 -0800 Subject: [PATCH 20/21] Pass haveCredentials/accessToken between client/caseCorrect and client/encrypt --- src/client/caseCorrect.js | 8 ++++---- src/client/encrypt.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/caseCorrect.js b/src/client/caseCorrect.js index d27f4b9..2eb634a 100644 --- a/src/client/caseCorrect.js +++ b/src/client/caseCorrect.js @@ -3,10 +3,10 @@ var UUID = require('uuid-1345'); module.exports = function(client, options) { var clientToken = options.clientToken || UUID.v4().toString(); - var accessToken; - var haveCredentials = options.password != null || (clientToken != null && options.session != null); + options.accessToken = null; + options.haveCredentials = options.password != null || (clientToken != null && options.session != null); - if(haveCredentials) { + if(options.haveCredentials) { // make a request to get the case-correct username before connecting. var cb = function(err, session) { if(err) { @@ -14,7 +14,7 @@ module.exports = function(client, options) { } else { client.session = session; client.username = session.selectedProfile.name; - accessToken = session.accessToken; + options.accessToken = session.accessToken; client.emit('session'); options.connect(client); } diff --git a/src/client/encrypt.js b/src/client/encrypt.js index c5c4443..696ddfc 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -16,7 +16,7 @@ module.exports = function(client, options) { client.end(); return; } - if(haveCredentials) { + if(options.haveCredentials) { joinServerRequest(onJoinServerResponse); } else { if(packet.serverId != '-') { @@ -35,7 +35,7 @@ module.exports = function(client, options) { } function joinServerRequest(cb) { - yggserver.join(accessToken, client.session.selectedProfile.id, + yggserver.join(options.accessToken, client.session.selectedProfile.id, packet.serverId, sharedSecret, packet.publicKey, cb); } From c3d86d7674fd44a5a40078f89c5931034b561def Mon Sep 17 00:00:00 2001 From: deathcap Date: Tue, 26 Jan 2016 01:05:32 -0800 Subject: [PATCH 21/21] Add client.connect(port, host) for backwards-compatibility Would like to remove this method because it is hardcoded to dependent on TCP/IP, but the serverTest uses it. Load tcp_dns as needed. --- src/client.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client.js b/src/client.js index 200d738..8a0a129 100644 --- a/src/client.js +++ b/src/client.js @@ -215,6 +215,13 @@ class Client extends EventEmitter else this.compressor.write(buffer); } + + // TCP/IP-specific (not generic Stream) method for backwards-compatibility + connect(port, host) { + var options = {port, host}; + require('./client/tcp_dns')(this, options); + options.connect(this); + } } module.exports = Client;