From 1226f0f5208c9779f15c196b6e14d4d5a5a4fc87 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Mon, 27 Jul 2015 02:26:36 +0200 Subject: [PATCH] split index.js into mainly createClient.js and createServer.js 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 --- src/createClient.js | 170 ++++++++++++++++++ src/createServer.js | 210 ++++++++++++++++++++++ src/index.js | 424 ++------------------------------------------ src/mcHexDigest.js | 30 ++++ src/ursa.js | 9 + src/version.js | 4 + 6 files changed, 435 insertions(+), 412 deletions(-) create mode 100644 src/createClient.js create mode 100644 src/createServer.js create mode 100644 src/mcHexDigest.js create mode 100644 src/ursa.js create mode 100644 src/version.js diff --git a/src/createClient.js b/src/createClient.js new file mode 100644 index 0000000..86ad82b --- /dev/null +++ b/src/createClient.js @@ -0,0 +1,170 @@ +var mcHexDigest=require("./mcHexDigest"); +var ursa=require("./ursa"); +var version = require("./version"); +var net = require('net'); +var dns = require('dns'); +var Client = require('./client'); +var assert = require('assert'); +var crypto = require('crypto'); +var Yggdrasil = require('./yggdrasil.js'); +var getSession = Yggdrasil.getSession; +var joinServer = Yggdrasil.joinServer; +var serializer = require("./transforms/serializer"); +var states = serializer.states; +var debug = require("./debug"); + +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 || Yggdrasil.generateUUID(); + var accessToken = options.accessToken || null; + + assert.ok(options.username, "username is required"); + var haveCredentials = options.password != null || (clientToken != null && accessToken != null); + var keepAlive = options.keepAlive == null ? true : options.keepAlive; + + + var client = new Client(false); + client.on('connect', onConnect); + if(keepAlive) client.on([states.PLAY, 0x00], onKeepAlive); + client.once([states.LOGIN, 0x01], onEncryptionKeyRequest); + client.once([states.LOGIN, 0x02], onLogin); + client.once("compress", onCompressionRequest); + client.on("set_compression", onCompressionRequest); + 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.username; + accessToken = session.accessToken; + client.emit('session'); + client.connect(port, host); + } + }; + + if(accessToken != null) getSession(options.username, accessToken, clientToken, true, cb); + else getSession(options.username, options.password, clientToken, false, cb); + } else { + // assume the server is in offline mode and just go for it. + client.username = options.username; + client.connect(port, host); + } + + return client; + + function onConnect() { + client.write(0x00, { + protocolVersion: version.version, + serverHost: host, + serverPort: port, + nextState: 2 + }); + client.state = states.LOGIN; + client.write(0x00, { + username: client.username + }); + } + + function onCompressionRequest(packet) { + client.compressionThreshold = packet.threshold; + } + + function onKeepAlive(packet) { + client.write(0x00, { + 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) { + var hash = crypto.createHash('sha1'); + hash.update(packet.serverId); + hash.update(sharedSecret); + hash.update(packet.publicKey); + + var digest = mcHexDigest(hash); + joinServer(client.username, digest, accessToken, client.session.selectedProfile.id, 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(0x01, { + 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'); +} diff --git a/src/createServer.js b/src/createServer.js new file mode 100644 index 0000000..bd728e4 --- /dev/null +++ b/src/createServer.js @@ -0,0 +1,210 @@ +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; +} diff --git a/src/index.js b/src/index.js index f9d8ea7..c54b469 100644 --- a/src/index.js +++ b/src/index.js @@ -1,42 +1,21 @@ -var assert = require('assert') - , crypto = require('crypto') - , bufferEqual = require('buffer-equal') - , Client = require('./client') - , dns = require('dns') - , net = require('net') - , Server = require('./server') - , Yggdrasil = require('./yggdrasil.js') - , getSession = Yggdrasil.getSession - , validateSession = Yggdrasil.validateSession - , joinServer = Yggdrasil.joinServer - , serializer = require("./transforms/serializer") - , states = serializer.states - , debug = require("./debug") - , utils = require("./utils") - ; - -var ursa; -try { - ursa = require("ursa"); -} catch(e) { - console.log("You are using a pure-javascript implementation of RSA."); - console.log("Your performance might be subpar. Please consider installing URSA"); - ursa = require("ursa-purejs"); -} - -var version = 47; -var minecraftVersion = '1.8.1'; - +var Client = require('./client'); +var Server = require('./server'); +var Yggdrasil = require('./yggdrasil.js'); +var serializer = require("./transforms/serializer"); +var utils = require("./utils"); +var version = require("./version"); var packets = require("../protocol/protocol"); var readPackets = require("./packets").readPackets; -var packetIndexes = readPackets(packets, states); +var packetIndexes = readPackets(packets, serializer.states); +var createClient = require("./createClient"); +var createServer = require("./createServer"); module.exports = { createClient: createClient, createServer: createServer, Client: Client, Server: Server, - states: states, + states: serializer.states, createPacketBuffer: serializer.createPacketBuffer, parsePacketData: serializer.parsePacketData, packetFields: packetIndexes.packetFields, @@ -48,385 +27,6 @@ module.exports = { evalCondition: utils.evalCondition, ping: require('./ping'), yggdrasil: Yggdrasil, - version: version, - minecraftVersion: minecraftVersion + version: version.version, + minecraftVersion: version.minecraftVersion }; - -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": minecraftVersion, - "protocol": 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; -} - -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 || Yggdrasil.generateUUID(); - var accessToken = options.accessToken || null; - - assert.ok(options.username, "username is required"); - var haveCredentials = options.password != null || (clientToken != null && accessToken != null); - var keepAlive = options.keepAlive == null ? true : options.keepAlive; - - - var client = new Client(false); - client.on('connect', onConnect); - if(keepAlive) client.on([states.PLAY, 0x00], onKeepAlive); - client.once([states.LOGIN, 0x01], onEncryptionKeyRequest); - client.once([states.LOGIN, 0x02], onLogin); - client.once("compress", onCompressionRequest); - client.on("set_compression", onCompressionRequest); - 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.username; - accessToken = session.accessToken; - client.emit('session'); - client.connect(port, host); - } - }; - - if(accessToken != null) getSession(options.username, accessToken, clientToken, true, cb); - else getSession(options.username, options.password, clientToken, false, cb); - } else { - // assume the server is in offline mode and just go for it. - client.username = options.username; - client.connect(port, host); - } - - return client; - - function onConnect() { - client.write(0x00, { - protocolVersion: version, - serverHost: host, - serverPort: port, - nextState: 2 - }); - client.state = states.LOGIN; - client.write(0x00, { - username: client.username - }); - } - - function onCompressionRequest(packet) { - client.compressionThreshold = packet.threshold; - } - - function onKeepAlive(packet) { - client.write(0x00, { - 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) { - var hash = crypto.createHash('sha1'); - hash.update(packet.serverId); - hash.update(sharedSecret); - hash.update(packet.publicKey); - - var digest = mcHexDigest(hash); - joinServer(client.username, digest, accessToken, client.session.selectedProfile.id, 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(0x01, { - 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'); -} - -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) & 0xff, i); - } else { - buffer.writeUInt8(newByte, i); - } - } - } -} diff --git a/src/mcHexDigest.js b/src/mcHexDigest.js new file mode 100644 index 0000000..4abf8f1 --- /dev/null +++ b/src/mcHexDigest.js @@ -0,0 +1,30 @@ +module.exports=mcHexDigest; + +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) & 0xff, i); + } else { + buffer.writeUInt8(newByte, i); + } + } + } +} diff --git a/src/ursa.js b/src/ursa.js new file mode 100644 index 0000000..fe35730 --- /dev/null +++ b/src/ursa.js @@ -0,0 +1,9 @@ +var ursa; +try { + ursa = require("ursa"); +} catch(e) { + console.log("You are using a pure-javascript implementation of RSA."); + console.log("Your performance might be subpar. Please consider installing URSA"); + ursa = require("ursa-purejs"); +} +module.exports=ursa; diff --git a/src/version.js b/src/version.js new file mode 100644 index 0000000..527a2e9 --- /dev/null +++ b/src/version.js @@ -0,0 +1,4 @@ +module.exports={ + version:47, + minecraftVersion:'1.8.1' +};