mirror of
https://github.com/unmojang/node-minecraft-protocol.git
synced 2025-09-27 13:14:41 -04:00
Merge branch 'master' into clientstream
This commit is contained in:
commit
dccb9cef58
@ -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();
|
||||
|
@ -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') {
|
||||
|
@ -3,8 +3,7 @@ var gulp = require('gulp');
|
||||
var plumber = require('gulp-plumber');
|
||||
var babel = require('gulp-babel');
|
||||
var options = {
|
||||
stage: 0, // Dat ES7 goodness
|
||||
optional: ["runtime"]
|
||||
presets: ['es2015']
|
||||
};
|
||||
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
|
13
package.json
13
package.json
@ -28,12 +28,12 @@
|
||||
},
|
||||
"browser": "browser.js",
|
||||
"devDependencies": {
|
||||
"chai": "^2.3.0",
|
||||
"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",
|
||||
@ -42,18 +42,13 @@
|
||||
"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",
|
||||
"through": "^2.3.8",
|
||||
"ursa-purejs": "0.0.3",
|
||||
"uuid": "^2.0.1",
|
||||
"uuid-1345": "^0.99.6",
|
||||
"yggdrasil": "0.1.0"
|
||||
},
|
||||
|
@ -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';
|
||||
@ -76,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);
|
||||
@ -214,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;
|
||||
|
43
src/client/caseCorrect.js
Normal file
43
src/client/caseCorrect.js
Normal file
@ -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();
|
||||
options.accessToken = null;
|
||||
options.haveCredentials = options.password != null || (clientToken != null && options.session != null);
|
||||
|
||||
if(options.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;
|
||||
options.accessToken = session.accessToken;
|
||||
client.emit('session');
|
||||
options.connect(client);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
options.connect(client);
|
||||
}
|
||||
};
|
9
src/client/compress.js
Normal file
9
src/client/compress.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function(client, options) {
|
||||
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
|
||||
};
|
66
src/client/encrypt.js
Normal file
66
src/client/encrypt.js
Normal file
@ -0,0 +1,66 @@
|
||||
var crypto = require('crypto');
|
||||
var yggserver = require('yggdrasil').server({});
|
||||
var ursa=require("../ursa");
|
||||
var debug = require("../debug");
|
||||
|
||||
module.exports = function(client, options) {
|
||||
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(options.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(options.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');
|
||||
}
|
20
src/client/keepalive.js
Normal file
20
src/client/keepalive.js
Normal file
@ -0,0 +1,20 @@
|
||||
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;
|
||||
|
||||
function onKeepAlive(packet) {
|
||||
if (timeout)
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => client.end(), checkTimeoutInterval);
|
||||
client.write('keep_alive', {
|
||||
keepAliveId: packet.keepAliveId
|
||||
});
|
||||
}
|
||||
|
||||
};
|
11
src/client/play.js
Normal file
11
src/client/play.js
Normal file
@ -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;
|
||||
}
|
||||
};
|
21
src/client/setProtocol.js
Normal file
21
src/client/setProtocol.js
Normal file
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
21
src/client/tcp_dns.js
Normal file
21
src/client/tcp_dns.js
Normal file
@ -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(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));
|
||||
} else {
|
||||
client.setSocket(net.connect(options.port, options.host));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
client.setSocket(net.connect(options.port, options.host));
|
||||
}
|
||||
};
|
||||
};
|
@ -1,158 +1,36 @@
|
||||
var ursa=require("./ursa");
|
||||
var net = require('net');
|
||||
var dns = require('dns');
|
||||
var Client = require('./client');
|
||||
var createClientStream = require('./createClientStream');
|
||||
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');
|
||||
|
||||
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');
|
||||
var play = require('./client/play');
|
||||
var tcp_dns = require('./client/tcp_dns');
|
||||
|
||||
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");
|
||||
var port = options.port || 25565;
|
||||
var host = options.host || 'localhost';
|
||||
var clientToken = options.clientToken || uuid.v4();
|
||||
var accessToken;
|
||||
|
||||
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);
|
||||
var version = mcData.version;
|
||||
options.majorVersion = version.majorVersion;
|
||||
options.protocolVersion = version.version;
|
||||
|
||||
var client = createClientStream(options);
|
||||
client.on('connect', onConnect);
|
||||
client.once('encryption_begin', onEncryptionKeyRequest);
|
||||
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);
|
||||
}
|
||||
};
|
||||
var client = new Client(false, options.majorVersion);
|
||||
|
||||
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);
|
||||
}
|
||||
tcp_dns(client, options);
|
||||
setProtocol(client, options);
|
||||
keepalive(client, options);
|
||||
encrypt(client, options);
|
||||
play(client, options);
|
||||
compress(client, options);
|
||||
caseCorrect(client, options);
|
||||
|
||||
var timeout = null;
|
||||
return client;
|
||||
|
||||
function onConnect() {
|
||||
client.write('set_protocol', {
|
||||
protocolVersion: version.version,
|
||||
serverHost: host,
|
||||
serverPort: port,
|
||||
nextState: 2
|
||||
});
|
||||
client.state = states.LOGIN;
|
||||
client.write('login_start', {
|
||||
username: client.username
|
||||
});
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
19
src/ping.js
19
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,15 +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;
|
||||
});
|
||||
|
||||
client.connect(port, host);
|
||||
tcp_dns(client, options);
|
||||
options.connect(client);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user