var EventEmitter = require('events').EventEmitter , util = require('util') , assert = require('assert') , ursa = require('ursa') , crypto = require('crypto') , bufferEqual = require('buffer-equal') , superagent = require('superagent') , protocol = require('./lib/protocol') , Client = require('./lib/client') , Server = require('./lib/server') module.exports = { createClient: createClient, createServer: createServer, Client: Client, Server: Server, ping: require('./lib/ping'), protocol: protocol, }; function createServer(options) { options = options || {}; var port = options.port != null ? options.port : options['server-port'] != null ? options['server-port'] : 25565 ; var maxPlayers = options['max-players'] || 20; var host = options.host || '0.0.0.0'; var kickTimeout = options.kickTimeout || 10 * 1000; var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000; var motd = options.motd || "A Minecraft server"; var onlineMode = options['online-mode'] == null ? true : options['online-mode']; var encryptionEnabled = options.encryption == null ? true : options.encryption; var serverKey = ursa.generatePrivateKey(1024); var server = new Server(options); server.onlineModeExceptions = {}; server.maxPlayers = maxPlayers; server.on("connection", function(client) { client.once(0xfe, onPing); client.once(0x02, onHandshake); client.once(0xFC, onEncryptionKeyResponse); 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) { if (loggedIn) return; client.write(0xff, { reason: [ 'ยง1', protocol.version, protocol.minecraftVersion, motd, server.playerCount, maxPlayers, ].join('\u0000') }); } function onHandshake(packet) { client.username = packet.username; var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException); var serverId; if (needToVerify) { serverId = crypto.randomBytes(4).toString('hex'); } else { serverId = '-'; } if (encryptionEnabled) { client.verifyToken = crypto.randomBytes(4); var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n"); var publicKeyStr = ""; for (var i=1;i 0) { pem += base64PubKey.substring(0, maxLineLength) + "\n"; base64PubKey = base64PubKey.substring(maxLineLength); } pem += "-----END PUBLIC KEY-----\n"; return ursa.createPublicKey(pem, 'utf8'); } function mcHexDigest(hash) { var buffer = new Buffer(hash.digest(), 'binary'); // check for negative hashes var negative = buffer.readInt8(0) < 0; if (negative) performTwosCompliment(buffer); var digest = buffer.toString('hex'); // trim leading zeroes digest = digest.replace(/^0+/g, ''); if (negative) digest = '-' + digest; return digest; function performTwosCompliment(buffer) { var carry = true; var i, newByte, value; for (i = buffer.length - 1; i >= 0; --i) { value = buffer.readUInt8(i); newByte = ~value & 0xff; if (carry) { carry = newByte === 0xff; buffer.writeUInt8(newByte + 1, i); } else { buffer.writeUInt8(newByte, i); } } } } function getLoginSession(email, password, cb) { var req = superagent.post("https://login.minecraft.net"); req.type('form'); req.send({ user: email, password: password, version: protocol.sessionVersion, }); req.end(function(err, resp) { var myErr; if (err) { cb(err); } else if (resp.serverError) { myErr = new Error("login.minecraft.net is broken: " + resp.status); myErr.code = 'ELOGIN500'; cb(myErr); } else if (resp.clientError) { myErr = new Error("login.minecraft.net rejected request: " + resp.status + " " + resp.text); myErr.code = 'ELOGIN400'; cb(myErr); } else { var values = resp.text.split(':'); var session = { currentGameVersion: values[0], username: values[2], id: values[3], uid: values[4], }; if (session.id && session.username) { cb(null, session); } else { myErr = new Error("login.minecraft.net rejected request: " + resp.status + " " + resp.text); myErr.code = 'ELOGIN400'; cb(myErr); } } }); }