mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2024-12-02 12:06:53 -05:00
1226f0f520
also create a few other files containing common information and functions : * mcHexDigest.js which contain the function of the same name used in both createClient.js and createServer.js * ursa.js which handle the abstraction on top of ursa and ursa-purejs : used in both createClient.js and createServer.js * version.js used in index.js, createClient.js and createServer.js
210 lines
6.7 KiB
JavaScript
210 lines
6.7 KiB
JavaScript
var mcHexDigest=require("./mcHexDigest");
|
|
var ursa=require("./ursa");
|
|
var version = require("./version");
|
|
var crypto = require('crypto');
|
|
var Yggdrasil = require('./yggdrasil.js');
|
|
var validateSession = Yggdrasil.validateSession;
|
|
var serializer = require("./transforms/serializer");
|
|
var states = serializer.states;
|
|
var bufferEqual = require('buffer-equal');
|
|
var Server = require('./server');
|
|
|
|
module.exports=createServer;
|
|
|
|
function createServer(options) {
|
|
options = options || {};
|
|
var port = options.port != null ?
|
|
options.port :
|
|
options['server-port'] != null ?
|
|
options['server-port'] :
|
|
25565;
|
|
var host = options.host || '0.0.0.0';
|
|
var kickTimeout = options.kickTimeout || 10 * 1000;
|
|
var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000;
|
|
var onlineMode = options['online-mode'] == null ? true : options['online-mode'];
|
|
// a function receiving the default status object and the client
|
|
// and returning a modified response object.
|
|
var beforePing = options.beforePing || null;
|
|
|
|
var serverKey = ursa.generatePrivateKey(1024);
|
|
|
|
var server = new Server(options);
|
|
server.motd = options.motd || "A Minecraft server";
|
|
server.maxPlayers = options['max-players'] || 20;
|
|
server.playerCount = 0;
|
|
server.onlineModeExceptions = {};
|
|
server.on("connection", function(client) {
|
|
client.once([states.HANDSHAKING, 0x00], onHandshake);
|
|
client.once([states.LOGIN, 0x00], onLogin);
|
|
client.once([states.STATUS, 0x00], onPing);
|
|
client.on('end', onEnd);
|
|
|
|
var keepAlive = false;
|
|
var loggedIn = false;
|
|
var lastKeepAlive = null;
|
|
|
|
var keepAliveTimer = null;
|
|
var loginKickTimer = setTimeout(kickForNotLoggingIn, kickTimeout);
|
|
|
|
var hash;
|
|
|
|
function kickForNotLoggingIn() {
|
|
client.end('LoginTimeout');
|
|
}
|
|
|
|
function keepAliveLoop() {
|
|
if(!keepAlive)
|
|
return;
|
|
|
|
// check if the last keepAlive was too long ago (kickTimeout)
|
|
var elapsed = new Date() - lastKeepAlive;
|
|
if(elapsed > kickTimeout) {
|
|
client.end('KeepAliveTimeout');
|
|
return;
|
|
}
|
|
client.write(0x00, {
|
|
keepAliveId: Math.floor(Math.random() * 2147483648)
|
|
});
|
|
}
|
|
|
|
function onKeepAlive(packet) {
|
|
lastKeepAlive = new Date();
|
|
}
|
|
|
|
function startKeepAlive() {
|
|
keepAlive = true;
|
|
lastKeepAlive = new Date();
|
|
keepAliveTimer = setInterval(keepAliveLoop, checkTimeoutInterval);
|
|
client.on(0x00, onKeepAlive);
|
|
}
|
|
|
|
function onEnd() {
|
|
clearInterval(keepAliveTimer);
|
|
clearTimeout(loginKickTimer);
|
|
}
|
|
|
|
function onPing(packet) {
|
|
var response = {
|
|
"version": {
|
|
"name": version.minecraftVersion,
|
|
"protocol": version.version
|
|
},
|
|
"players": {
|
|
"max": server.maxPlayers,
|
|
"online": server.playerCount,
|
|
"sample": []
|
|
},
|
|
"description": {"text": server.motd},
|
|
"favicon": server.favicon
|
|
};
|
|
|
|
if(beforePing) {
|
|
response = beforePing(response, client) || response;
|
|
}
|
|
|
|
client.once([states.STATUS, 0x01], function(packet) {
|
|
client.write(0x01, {time: packet.time});
|
|
client.end();
|
|
});
|
|
client.write(0x00, {response: JSON.stringify(response)});
|
|
}
|
|
|
|
function onLogin(packet) {
|
|
client.username = packet.username;
|
|
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
|
var needToVerify = (onlineMode && !isException) || (!onlineMode && isException);
|
|
if(needToVerify) {
|
|
var serverId = crypto.randomBytes(4).toString('hex');
|
|
client.verifyToken = crypto.randomBytes(4);
|
|
var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n");
|
|
var publicKeyStr = "";
|
|
for(var i = 1; i < publicKeyStrArr.length - 2; i++) {
|
|
publicKeyStr += publicKeyStrArr[i]
|
|
}
|
|
client.publicKey = new Buffer(publicKeyStr, 'base64');
|
|
hash = crypto.createHash("sha1");
|
|
hash.update(serverId);
|
|
client.once([states.LOGIN, 0x01], onEncryptionKeyResponse);
|
|
client.write(0x01, {
|
|
serverId: serverId,
|
|
publicKey: client.publicKey,
|
|
verifyToken: client.verifyToken
|
|
});
|
|
} else {
|
|
loginClient();
|
|
}
|
|
}
|
|
|
|
function onHandshake(packet) {
|
|
client.serverHost = packet.serverHost;
|
|
client.serverPort = packet.serverPort;
|
|
client.protocolVersion = packet.protocolVersion;
|
|
if(packet.nextState == 1) {
|
|
client.state = states.STATUS;
|
|
} else if(packet.nextState == 2) {
|
|
client.state = states.LOGIN;
|
|
}
|
|
}
|
|
|
|
function onEncryptionKeyResponse(packet) {
|
|
try {
|
|
var verifyToken = serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
|
if(!bufferEqual(client.verifyToken, verifyToken)) {
|
|
client.end('DidNotEncryptVerifyTokenProperly');
|
|
return;
|
|
}
|
|
var sharedSecret = serverKey.decrypt(packet.sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
|
} catch(e) {
|
|
client.end('DidNotEncryptVerifyTokenProperly');
|
|
return;
|
|
}
|
|
client.setEncryption(sharedSecret);
|
|
hash.update(sharedSecret);
|
|
hash.update(client.publicKey);
|
|
|
|
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
|
var needToVerify = (onlineMode && !isException) || (!onlineMode && isException);
|
|
var nextStep = needToVerify ? verifyUsername : loginClient;
|
|
nextStep();
|
|
|
|
function verifyUsername() {
|
|
var digest = mcHexDigest(hash);
|
|
validateSession(client.username, digest, function(err, uuid) {
|
|
if(err) {
|
|
client.end("Failed to verify username!");
|
|
return;
|
|
}
|
|
client.uuid = uuid;
|
|
// Convert to a valid UUID until the session server updates and does
|
|
// it automatically
|
|
client.uuid = client.uuid.replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
|
|
loginClient();
|
|
});
|
|
}
|
|
}
|
|
|
|
function loginClient() {
|
|
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
|
if(onlineMode == false || isException) {
|
|
client.uuid = "0-0-0-0-0";
|
|
}
|
|
//client.write('compress', { threshold: 256 }); // Default threshold is 256
|
|
//client.compressionThreshold = 256;
|
|
client.write(0x02, {uuid: client.uuid, username: client.username});
|
|
client.state = states.PLAY;
|
|
loggedIn = true;
|
|
startKeepAlive();
|
|
|
|
clearTimeout(loginKickTimer);
|
|
loginKickTimer = null;
|
|
|
|
server.playerCount += 1;
|
|
client.once('end', function() {
|
|
server.playerCount -= 1;
|
|
});
|
|
server.emit('login', client);
|
|
}
|
|
});
|
|
server.listen(port, host);
|
|
return server;
|
|
}
|