mirror of
https://github.com/unmojang/node-minecraft-protocol.git
synced 2025-09-28 13:45:37 -04:00
Merge branch 'master' into forge (client modularization)
Merging https://github.com/PrismarineJS/node-minecraft-protocol/pull/333
This commit is contained in:
commit
fd9caddb67
@ -215,6 +215,13 @@ class Client extends EventEmitter
|
|||||||
else
|
else
|
||||||
this.compressor.write(buffer);
|
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;
|
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;
|
||||||
|
}
|
||||||
|
};
|
24
src/client/setProtocol.js
Normal file
24
src/client/setProtocol.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
var states = require("../states");
|
||||||
|
|
||||||
|
module.exports = function(client, options) {
|
||||||
|
client.on('connect', onConnect);
|
||||||
|
|
||||||
|
function onConnect() {
|
||||||
|
var taggedHost = options.host;
|
||||||
|
if (options.tagHost) taggedHost += options.tagHost;
|
||||||
|
|
||||||
|
client.write('set_protocol', {
|
||||||
|
protocolVersion: options.protocolVersion,
|
||||||
|
serverHost: taggedHost,
|
||||||
|
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,185 +1,35 @@
|
|||||||
var ursa=require("./ursa");
|
|
||||||
var net = require('net');
|
|
||||||
var dns = require('dns');
|
|
||||||
var Client = require('./client');
|
var Client = require('./client');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var crypto = require('crypto');
|
|
||||||
var yggdrasil = require('yggdrasil')({});
|
var encrypt = require('./client/encrypt');
|
||||||
var yggserver = require('yggdrasil').server({});
|
var keepalive = require('./client/keepalive');
|
||||||
var states = require("./states");
|
var compress = require('./client/compress');
|
||||||
var debug = require("./debug");
|
var caseCorrect = require('./client/caseCorrect');
|
||||||
var UUID = require('uuid-1345');
|
var setProtocol = require('./client/setProtocol');
|
||||||
|
var play = require('./client/play');
|
||||||
|
var tcp_dns = require('./client/tcp_dns');
|
||||||
|
|
||||||
module.exports=createClient;
|
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) {
|
function createClient(options) {
|
||||||
assert.ok(options, "options is required");
|
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;
|
|
||||||
|
|
||||||
assert.ok(options.username, "username is required");
|
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 optVersion = options.version || require("./version").defaultVersion;
|
||||||
var mcData=require("minecraft-data")(optVersion);
|
var mcData=require("minecraft-data")(optVersion);
|
||||||
var version = mcData.version;
|
var version = mcData.version;
|
||||||
|
options.majorVersion = version.majorVersion;
|
||||||
|
options.protocolVersion = version.version;
|
||||||
|
|
||||||
|
var client = new Client(false, options.majorVersion);
|
||||||
|
|
||||||
var client = new Client(false,version.majorVersion);
|
tcp_dns(client, options);
|
||||||
client.on('connect', onConnect);
|
setProtocol(client, options);
|
||||||
if(keepAlive) client.on('keep_alive', onKeepAlive);
|
keepalive(client, options);
|
||||||
client.once('encryption_begin', onEncryptionKeyRequest);
|
encrypt(client, options);
|
||||||
client.once('success', onLogin);
|
play(client, options);
|
||||||
client.once("compress", onCompressionRequest);
|
compress(client, options);
|
||||||
client.on("set_compression", onCompressionRequest);
|
caseCorrect(client, options);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeout = null;
|
|
||||||
return client;
|
return client;
|
||||||
|
|
||||||
function onConnect() {
|
|
||||||
var taggedHost = host;
|
|
||||||
if (options.tagHost) taggedHost += options.tagHost;
|
|
||||||
|
|
||||||
client.write('set_protocol', {
|
|
||||||
protocolVersion: version.version,
|
|
||||||
serverHost: taggedHost,
|
|
||||||
serverPort: port,
|
|
||||||
nextState: 2
|
|
||||||
});
|
|
||||||
client.state = states.LOGIN;
|
|
||||||
client.write('login_start', {
|
|
||||||
username: client.username
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
19
src/ping.js
19
src/ping.js
@ -1,17 +1,20 @@
|
|||||||
var net = require('net');
|
var net = require('net');
|
||||||
var Client = require('./client');
|
var Client = require('./client');
|
||||||
var states = require("./states");
|
var states = require("./states");
|
||||||
|
var tcp_dns = require('./client/tcp_dns');
|
||||||
|
|
||||||
module.exports = ping;
|
module.exports = ping;
|
||||||
|
|
||||||
function ping(options, cb) {
|
function ping(options, cb) {
|
||||||
var host = options.host || 'localhost';
|
options.host = options.host || 'localhost';
|
||||||
var port = options.port || 25565;
|
options.port = options.port || 25565;
|
||||||
var optVersion = options.version || require("./version").defaultVersion;
|
var optVersion = options.version || require("./version").defaultVersion;
|
||||||
var mcData=require("minecraft-data")(optVersion);
|
var mcData=require("minecraft-data")(optVersion);
|
||||||
var version = mcData.version;
|
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) {
|
client.on('error', function(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
@ -32,15 +35,17 @@ function ping(options, cb) {
|
|||||||
client.write('ping_start', {});
|
client.write('ping_start', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: refactor with src/client/setProtocol.js
|
||||||
client.on('connect', function() {
|
client.on('connect', function() {
|
||||||
client.write('set_protocol', {
|
client.write('set_protocol', {
|
||||||
protocolVersion: version.version,
|
protocolVersion: options.protocolVersion,
|
||||||
serverHost: host,
|
serverHost: options.host,
|
||||||
serverPort: port,
|
serverPort: options.port,
|
||||||
nextState: 1
|
nextState: 1
|
||||||
});
|
});
|
||||||
client.state = states.STATUS;
|
client.state = states.STATUS;
|
||||||
});
|
});
|
||||||
|
|
||||||
client.connect(port, host);
|
tcp_dns(client, options);
|
||||||
|
options.connect(client);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user