mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2025-05-18 09:10:26 -04:00
commit
c6d228d7de
13 changed files with 1353 additions and 858 deletions
163
examples/client_chat.js
Normal file
163
examples/client_chat.js
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
var readline = require('readline');
|
||||||
|
var color = require("ansi-color").set;
|
||||||
|
var mc = require('../');
|
||||||
|
var states = mc.protocol.states;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var colors = new Array();
|
||||||
|
colors["black"] = 'black+white_bg';
|
||||||
|
colors["dark_blue"] = 'blue';
|
||||||
|
colors["dark_green"] = 'green';
|
||||||
|
colors["dark_aqua"] = 'cyan'
|
||||||
|
colors["dark_red"] = 'red'
|
||||||
|
colors["dark_purple"] = 'magenta'
|
||||||
|
colors["gold"] = 'yellow'
|
||||||
|
colors["gray"] = 'black+white_bg'
|
||||||
|
colors["dark_gray"] = 'black+white_bg'
|
||||||
|
colors["blue"] = 'blue'
|
||||||
|
colors["green"] = 'green'
|
||||||
|
colors["aqua"] = 'cyan'
|
||||||
|
colors["red"] = 'red'
|
||||||
|
colors["light_purple"] = 'magenta'
|
||||||
|
colors["yellow"] = 'yellow'
|
||||||
|
colors["white"] = 'white'
|
||||||
|
colors["obfuscated"] = 'blink'
|
||||||
|
colors["bold"] = 'bold'
|
||||||
|
colors["strikethrough"] = ''
|
||||||
|
colors["underlined"] = 'underlined'
|
||||||
|
colors["italic"] = ''
|
||||||
|
colors["reset"] = 'white+black_bg'
|
||||||
|
|
||||||
|
var dictionary = {};
|
||||||
|
dictionary["chat.stream.emote"] = "(%s) * %s %s";
|
||||||
|
dictionary["chat.stream.text"] = "(%s) <%s> %s";
|
||||||
|
dictionary["chat.type.achievement"] = "%s has just earned the achievement %s";
|
||||||
|
dictionary["chat.type.admin"] = "[%s: %s]";
|
||||||
|
dictionary["chat.type.announcement"] = "[%s] %s";
|
||||||
|
dictionary["chat.type.emote"] = "* %s %s";
|
||||||
|
dictionary["chat.type.text"] = "<%s> %s";
|
||||||
|
|
||||||
|
var rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
terminal: false
|
||||||
|
});
|
||||||
|
|
||||||
|
function print_help() {
|
||||||
|
console.log("usage: node minechat.js <hostname> <user> <password>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.length < 5) {
|
||||||
|
console.log("Too few arguments!");
|
||||||
|
print_help();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.argv.forEach(function(val, index, array) {
|
||||||
|
if (val == "-h") {
|
||||||
|
print_help();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var host = process.argv[2];
|
||||||
|
var port = 25565;
|
||||||
|
var user = process.argv[3];
|
||||||
|
var passwd = process.argv[4];
|
||||||
|
|
||||||
|
if (host.indexOf(':') != -1) {
|
||||||
|
port = host.substring(host.indexOf(':')+1);
|
||||||
|
host = host.substring(0, host.indexOf(':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("connecting to " + host + ":" + port);
|
||||||
|
console.log("user: " + user);
|
||||||
|
console.log("passwd: " + Array(passwd.length).join('*'));
|
||||||
|
|
||||||
|
var client = mc.createClient({
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
username: user,
|
||||||
|
password: passwd
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on([states.PLAY, 0x40], function(packet) {
|
||||||
|
console.info(color('Kicked for ' + packet.reason, "blink+red"));
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
var chats = [];
|
||||||
|
|
||||||
|
client.on('connect', function() {
|
||||||
|
console.info(color('Successfully connected to ' + host + ':' + port, "blink+green"));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('state', function(newState) {
|
||||||
|
if (newState === states.PLAY) {
|
||||||
|
chats.forEach(function(chat) {
|
||||||
|
client.write(0x01, {message: chat});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.on('line', function(line) {
|
||||||
|
if(line == '') {
|
||||||
|
return;
|
||||||
|
} else if(line == '/quit') {
|
||||||
|
var reason = 'disconnect.quitting';
|
||||||
|
console.info('Disconnected from ' + host + ':' + port);
|
||||||
|
client.write([states.PLAY, 0x40], { reason: reason });
|
||||||
|
return;
|
||||||
|
} else if(line == '/end') {
|
||||||
|
console.info('Forcibly ended client');
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!client.write([states.PLAY, 0x01], { message: line })) {
|
||||||
|
chats.push(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on([states.PLAY, 0x02], function(packet) {
|
||||||
|
var j = JSON.parse(packet.message);
|
||||||
|
var chat = parseChat(j, {});
|
||||||
|
console.info(chat);
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseChat(chatObj, parentState) {
|
||||||
|
function getColorize(parentState) {
|
||||||
|
var myColor = "";
|
||||||
|
if ('color' in parentState) myColor += colors[parentState.color] + "+";
|
||||||
|
if (parentState.bold) myColor += "bold+";
|
||||||
|
if (parentState.underlined) myColor += "underline+";
|
||||||
|
if (parentState.obfuscated) myColor += "obfuscated+";
|
||||||
|
if (myColor.length > 0) myColor = myColor.slice(0,-1);
|
||||||
|
return myColor;
|
||||||
|
}
|
||||||
|
if (typeof chatObj === "string") {
|
||||||
|
return color(chatObj, getColorize(parentState));
|
||||||
|
} else {
|
||||||
|
var chat = "";
|
||||||
|
if ('color' in chatObj) parentState.color = chatObj['color'];
|
||||||
|
if ('bold' in chatObj) parentState.bold = chatObj['bold'];
|
||||||
|
if ('italic' in chatObj) parentState.italic = chatObj['italic'];
|
||||||
|
if ('underlined' in chatObj) parentState.underlined = chatObj['underlined'];
|
||||||
|
if ('strikethrough' in chatObj) parentState.strikethrough = chatObj['strikethrough'];
|
||||||
|
if ('obfuscated' in chatObj) parentState.obfuscated = chatObj['obfuscated'];
|
||||||
|
|
||||||
|
if ('text' in chatObj) {
|
||||||
|
chat += color(chatObj.text, getColorize(parentState));
|
||||||
|
} else if ('translate' in chatObj && dictionary.hasOwnProperty(chatObj.translate)) {
|
||||||
|
var args = [dictionary[chatObj.translate]];
|
||||||
|
chatObj['with'].forEach(function(s) {
|
||||||
|
args.push(parseChat(s, parentState));
|
||||||
|
});
|
||||||
|
|
||||||
|
chat += color(util.format.apply(this, args), getColorize(parentState));
|
||||||
|
}
|
||||||
|
for (var i in chatObj.extra) {
|
||||||
|
chat += parseChat(chatObj.extra[i], parentState);
|
||||||
|
}
|
||||||
|
return chat;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
var mc = require('../');
|
var mc = require('../')
|
||||||
|
, states = mc.protocol.states
|
||||||
|
|
||||||
var client = mc.createClient({
|
var client = mc.createClient({
|
||||||
username: process.env.MC_USERNAME,
|
username: process.env.MC_USERNAME,
|
||||||
password: process.env.MC_PASSWORD,
|
password: process.env.MC_PASSWORD,
|
||||||
|
@ -6,12 +8,12 @@ var client = mc.createClient({
|
||||||
client.on('connect', function() {
|
client.on('connect', function() {
|
||||||
console.info('connected');
|
console.info('connected');
|
||||||
});
|
});
|
||||||
client.on(0x03, function(packet) {
|
client.on([states.PLAY, 0x02], function(packet) {
|
||||||
var jsonMsg = JSON.parse(packet.message);
|
var jsonMsg = JSON.parse(packet.message);
|
||||||
if (jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') {
|
if (jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') {
|
||||||
var username = jsonMsg.using[0];
|
var username = jsonMsg.with[0];
|
||||||
var msg = jsonMsg.using[1];
|
var msg = jsonMsg.with[1];
|
||||||
if (username === client.username) return;
|
if (username === client.username) return;
|
||||||
client.write(0x03, {message: msg});
|
client.write(0x01, {message: msg});
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
var mc = require('../');
|
var mc = require('../');
|
||||||
|
var states = mc.protocol.states;
|
||||||
|
|
||||||
var yellow = '§e';
|
var yellow = '§e';
|
||||||
|
|
||||||
|
@ -30,17 +31,16 @@ server.on('login', function(client) {
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
maxPlayers: server.maxPlayers
|
maxPlayers: server.maxPlayers
|
||||||
});
|
});
|
||||||
client.write(0x0d, {
|
client.write(0x08, {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 256,
|
y: 256,
|
||||||
stance: 255,
|
|
||||||
z: 0,
|
z: 0,
|
||||||
yaw: 0,
|
yaw: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
onGround: true
|
onGround: true
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on(0x03, function(data) {
|
client.on([states.PLAY, 0x01], function(data) {
|
||||||
var message = '<'+client.username+'>' + ' ' + data.message;
|
var message = '<'+client.username+'>' + ' ' + data.message;
|
||||||
broadcast(message, client, client.username);
|
broadcast(message, client, client.username);
|
||||||
console.log(message);
|
console.log(message);
|
||||||
|
@ -66,12 +66,12 @@ function broadcast(message, exclude, username) {
|
||||||
if (client !== exclude) {
|
if (client !== exclude) {
|
||||||
var msg = {
|
var msg = {
|
||||||
translate: translate,
|
translate: translate,
|
||||||
using: [
|
"with": [
|
||||||
username,
|
username,
|
||||||
'Hello, world!'
|
'Hello, world!'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
client.write(0x03, { message: JSON.stringify(msg) });
|
client.write(0x02, { message: JSON.stringify(msg) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var mc = require('../');
|
var mc = require('../');
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
'online-mode': false, // optional
|
// 'online-mode': false, // optional
|
||||||
};
|
};
|
||||||
|
|
||||||
var server = mc.createServer(options);
|
var server = mc.createServer(options);
|
||||||
|
@ -23,10 +23,9 @@ server.on('login', function(client) {
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
maxPlayers: server.maxPlayers
|
maxPlayers: server.maxPlayers
|
||||||
});
|
});
|
||||||
client.write(0x0d, {
|
client.write(0x08, {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 1.62,
|
y: 1.62,
|
||||||
stance: 0,
|
|
||||||
z: 0,
|
z: 0,
|
||||||
yaw: 0,
|
yaw: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
|
@ -35,12 +34,12 @@ server.on('login', function(client) {
|
||||||
|
|
||||||
var msg = {
|
var msg = {
|
||||||
translate: 'chat.type.announcement',
|
translate: 'chat.type.announcement',
|
||||||
using: [
|
"with": [
|
||||||
'Server',
|
'Server',
|
||||||
'Hello, world!'
|
'Hello, world!'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
client.write(0x03, { message: JSON.stringify(msg) });
|
client.write(0x02, { message: JSON.stringify(msg) });
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('error', function(error) {
|
server.on('error', function(error) {
|
||||||
|
|
247
index.js
247
index.js
|
@ -1,15 +1,20 @@
|
||||||
var EventEmitter = require('events').EventEmitter
|
var EventEmitter = require('events').EventEmitter
|
||||||
, util = require('util')
|
, util = require('util')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, ursa = require('ursa')
|
, ursa = require('ursa')
|
||||||
, crypto = require('crypto')
|
, crypto = require('crypto')
|
||||||
, bufferEqual = require('buffer-equal')
|
, bufferEqual = require('buffer-equal')
|
||||||
, superagent = require('superagent')
|
, superagent = require('superagent')
|
||||||
, protocol = require('./lib/protocol')
|
, protocol = require('./lib/protocol')
|
||||||
, Client = require('./lib/client')
|
, Client = require('./lib/client')
|
||||||
, Server = require('./lib/server')
|
, Server = require('./lib/server')
|
||||||
, debug = protocol.debug
|
, Yggdrasil = require('./lib/yggdrasil.js')
|
||||||
;
|
, getSession = Yggdrasil.getSession
|
||||||
|
, validateSession = Yggdrasil.validateSession
|
||||||
|
, joinServer = Yggdrasil.joinServer
|
||||||
|
, states = protocol.states
|
||||||
|
, debug = protocol.debug
|
||||||
|
;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createClient: createClient,
|
createClient: createClient,
|
||||||
|
@ -23,10 +28,10 @@ module.exports = {
|
||||||
function createServer(options) {
|
function createServer(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var port = options.port != null ?
|
var port = options.port != null ?
|
||||||
options.port :
|
options.port :
|
||||||
options['server-port'] != null ?
|
options['server-port'] != null ?
|
||||||
options['server-port'] :
|
options['server-port'] :
|
||||||
25565 ;
|
25565;
|
||||||
var host = options.host || '0.0.0.0';
|
var host = options.host || '0.0.0.0';
|
||||||
var kickTimeout = options.kickTimeout || 10 * 1000;
|
var kickTimeout = options.kickTimeout || 10 * 1000;
|
||||||
var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000;
|
var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000;
|
||||||
|
@ -41,9 +46,9 @@ function createServer(options) {
|
||||||
server.playerCount = 0;
|
server.playerCount = 0;
|
||||||
server.onlineModeExceptions = {};
|
server.onlineModeExceptions = {};
|
||||||
server.on("connection", function(client) {
|
server.on("connection", function(client) {
|
||||||
client.once(0xfe, onPing);
|
client.once([states.HANDSHAKING, 0x00], onHandshake);
|
||||||
client.once(0x02, onHandshake);
|
client.once([states.LOGIN, 0x00], onLogin);
|
||||||
client.once(0xFC, onEncryptionKeyResponse);
|
client.once([states.STATUS, 0x00], onPing);
|
||||||
client.on('end', onEnd);
|
client.on('end', onEnd);
|
||||||
|
|
||||||
var keepAlive = false;
|
var keepAlive = false;
|
||||||
|
@ -60,7 +65,8 @@ function createServer(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function keepAliveLoop() {
|
function keepAliveLoop() {
|
||||||
if (! keepAlive) return;
|
if (!keepAlive)
|
||||||
|
return;
|
||||||
|
|
||||||
// check if the last keepAlive was too long ago (kickTimeout)
|
// check if the last keepAlive was too long ago (kickTimeout)
|
||||||
var elapsed = new Date() - lastKeepAlive;
|
var elapsed = new Date() - lastKeepAlive;
|
||||||
|
@ -90,40 +96,44 @@ function createServer(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPing(packet) {
|
function onPing(packet) {
|
||||||
if (loggedIn) return;
|
var response = {
|
||||||
client.write(0xff, {
|
"version": {
|
||||||
reason: [
|
"name": protocol.minecraftVersion,
|
||||||
'§1',
|
"protocol": protocol.version
|
||||||
protocol.version,
|
},
|
||||||
protocol.minecraftVersion,
|
"players": {
|
||||||
server.motd,
|
"max": server.maxPlayers,
|
||||||
server.playerCount,
|
"online": server.playerCount,
|
||||||
server.maxPlayers,
|
"sample": []
|
||||||
].join('\u0000')
|
},
|
||||||
|
"description": {"text": server.motd},
|
||||||
|
"favicon": server.favicon
|
||||||
|
};
|
||||||
|
|
||||||
|
client.once([states.STATUS, 0x01], function(packet) {
|
||||||
|
client.write(0x01, { time: packet.time });
|
||||||
|
client.end();
|
||||||
});
|
});
|
||||||
|
client.write(0x00, {response: JSON.stringify(response)});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHandshake(packet) {
|
function onLogin(packet) {
|
||||||
client.username = packet.username;
|
client.username = packet.username;
|
||||||
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
||||||
var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
|
var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
|
||||||
var serverId;
|
if (encryptionEnabled || needToVerify) {
|
||||||
if (needToVerify) {
|
var serverId = crypto.randomBytes(4).toString('hex');
|
||||||
serverId = crypto.randomBytes(4).toString('hex');
|
|
||||||
} else {
|
|
||||||
serverId = '-';
|
|
||||||
}
|
|
||||||
if (encryptionEnabled) {
|
|
||||||
client.verifyToken = crypto.randomBytes(4);
|
client.verifyToken = crypto.randomBytes(4);
|
||||||
var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n");
|
var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n");
|
||||||
var publicKeyStr = "";
|
var publicKeyStr = "";
|
||||||
for (var i=1;i<publicKeyStrArr.length - 2;i++) {
|
for (var i = 1; i < publicKeyStrArr.length - 2; i++) {
|
||||||
publicKeyStr += publicKeyStrArr[i]
|
publicKeyStr += publicKeyStrArr[i]
|
||||||
}
|
}
|
||||||
client.publicKey = new Buffer(publicKeyStr,'base64');
|
client.publicKey = new Buffer(publicKeyStr, 'base64');
|
||||||
hash = crypto.createHash("sha1");
|
hash = crypto.createHash("sha1");
|
||||||
hash.update(serverId);
|
hash.update(serverId);
|
||||||
client.write(0xFD, {
|
client.once([states.LOGIN, 0x01], onEncryptionKeyResponse);
|
||||||
|
client.write(0x01, {
|
||||||
serverId: serverId,
|
serverId: serverId,
|
||||||
publicKey: client.publicKey,
|
publicKey: client.publicKey,
|
||||||
verifyToken: client.verifyToken
|
verifyToken: client.verifyToken
|
||||||
|
@ -133,9 +143,17 @@ function createServer(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHandshake(packet) {
|
||||||
|
if (packet.nextState == 1) {
|
||||||
|
client.state = states.STATUS;
|
||||||
|
} else if (packet.nextState == 2) {
|
||||||
|
client.state = states.LOGIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onEncryptionKeyResponse(packet) {
|
function onEncryptionKeyResponse(packet) {
|
||||||
var verifyToken = serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
var verifyToken = serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
||||||
if (! bufferEqual(client.verifyToken, verifyToken)) {
|
if (!bufferEqual(client.verifyToken, verifyToken)) {
|
||||||
client.end('DidNotEncryptVerifyTokenProperly');
|
client.end('DidNotEncryptVerifyTokenProperly');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -144,49 +162,29 @@ function createServer(options) {
|
||||||
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||||
hash.update(sharedSecret);
|
hash.update(sharedSecret);
|
||||||
hash.update(client.publicKey);
|
hash.update(client.publicKey);
|
||||||
client.write(0xFC, {
|
|
||||||
sharedSecret: new Buffer(0),
|
|
||||||
verifyToken: new Buffer(0)
|
|
||||||
});
|
|
||||||
client.encryptionEnabled = true;
|
client.encryptionEnabled = true;
|
||||||
|
|
||||||
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
||||||
var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
|
var needToVerify = (onlineMode && !isException) || (!onlineMode && isException);
|
||||||
var nextStep = needToVerify ? verifyUsername : loginClient;
|
var nextStep = needToVerify ? verifyUsername : loginClient;
|
||||||
nextStep();
|
nextStep();
|
||||||
|
|
||||||
function verifyUsername() {
|
function verifyUsername() {
|
||||||
var digest = mcHexDigest(hash);
|
var digest = mcHexDigest(hash);
|
||||||
var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp");
|
validateSession(client.username, digest, function(err, uuid) {
|
||||||
request.query({
|
|
||||||
user: client.username,
|
|
||||||
serverId: digest
|
|
||||||
});
|
|
||||||
request.end(function(err, resp) {
|
|
||||||
var myErr;
|
|
||||||
if (err) {
|
if (err) {
|
||||||
server.emit('error', err);
|
client.end("Failed to verify username!");
|
||||||
client.end('McSessionUnavailable');
|
return;
|
||||||
} else if (resp.serverError) {
|
|
||||||
myErr = new Error("session.minecraft.net is broken: " + resp.status);
|
|
||||||
myErr.code = 'EMCSESSION500';
|
|
||||||
server.emit('error', myErr);
|
|
||||||
client.end('McSessionDown');
|
|
||||||
} else if (resp.serverError) {
|
|
||||||
myErr = new Error("session.minecraft.net rejected request: " + resp.status);
|
|
||||||
myErr.code = 'EMCSESSION400';
|
|
||||||
server.emit('error', myErr);
|
|
||||||
client.end('McSessionRejectedAuthRequest');
|
|
||||||
} else if (resp.text !== "YES") {
|
|
||||||
client.end('FailedToVerifyUsername');
|
|
||||||
} else {
|
|
||||||
loginClient();
|
|
||||||
}
|
}
|
||||||
|
client.UUID = uuid;
|
||||||
|
loginClient();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loginClient() {
|
function loginClient() {
|
||||||
|
client.write(0x02, {uuid: (client.UUID | 0).toString(10), username: client.username});
|
||||||
|
client.state = states.PLAY;
|
||||||
loggedIn = true;
|
loggedIn = true;
|
||||||
startKeepAlive();
|
startKeepAlive();
|
||||||
|
|
||||||
|
@ -208,28 +206,36 @@ function createClient(options) {
|
||||||
assert.ok(options, "options is required");
|
assert.ok(options, "options is required");
|
||||||
var port = options.port || 25565;
|
var port = options.port || 25565;
|
||||||
var host = options.host || 'localhost';
|
var host = options.host || 'localhost';
|
||||||
|
var clientToken = options.clientToken || Yggdrasil.generateUUID();
|
||||||
|
var accessToken = options.accessToken || null;
|
||||||
|
|
||||||
assert.ok(options.username, "username is required");
|
assert.ok(options.username, "username is required");
|
||||||
var haveCredentials = options.password != null;
|
var haveCredentials = options.password != null || (clientToken != null && accessToken != null);
|
||||||
var keepAlive = options.keepAlive == null ? true : options.keepAlive;
|
var keepAlive = options.keepAlive == null ? true : options.keepAlive;
|
||||||
|
|
||||||
|
|
||||||
var client = new Client(false);
|
var client = new Client(false);
|
||||||
client.on('connect', onConnect);
|
client.on('connect', onConnect);
|
||||||
if (keepAlive) client.on(0x00, onKeepAlive);
|
if (keepAlive) client.on([states.PLAY, 0x00], onKeepAlive);
|
||||||
client.once(0xFC, onEncryptionKeyResponse);
|
client.once([states.LOGIN, 0x01], onEncryptionKeyRequest);
|
||||||
client.once(0xFD, onEncryptionKeyRequest);
|
client.once([states.LOGIN, 0x02], onLogin);
|
||||||
|
|
||||||
if (haveCredentials) {
|
if (haveCredentials) {
|
||||||
// make a request to get the case-correct username before connecting.
|
// make a request to get the case-correct username before connecting.
|
||||||
getLoginSession(options.username, options.password, function(err, session) {
|
var cb = function(err, session) {
|
||||||
if (err) {
|
if (err) {
|
||||||
client.emit('error', err);
|
client.emit('error', err);
|
||||||
} else {
|
} else {
|
||||||
client.session = session;
|
client.session = session;
|
||||||
client.username = session.username;
|
client.username = session.username;
|
||||||
|
accessToken = session.accessToken;
|
||||||
client.emit('session');
|
client.emit('session');
|
||||||
client.connect(port, host);
|
client.connect(port, host);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (accessToken != null) getSession(options.username, accessToken, options.clientToken, true, cb);
|
||||||
|
else getSession(options.username, options.password, options.clientToken, false, cb);
|
||||||
} else {
|
} else {
|
||||||
// assume the server is in offline mode and just go for it.
|
// assume the server is in offline mode and just go for it.
|
||||||
client.username = options.username;
|
client.username = options.username;
|
||||||
|
@ -239,11 +245,16 @@ function createClient(options) {
|
||||||
return client;
|
return client;
|
||||||
|
|
||||||
function onConnect() {
|
function onConnect() {
|
||||||
client.write(0x02, {
|
client.write(0x00, {
|
||||||
protocolVersion: protocol.version,
|
protocolVersion: protocol.version,
|
||||||
username: client.username,
|
|
||||||
serverHost: host,
|
serverHost: host,
|
||||||
serverPort: port,
|
serverPort: port,
|
||||||
|
nextState: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
client.state = states.LOGIN;
|
||||||
|
client.write(0x00, {
|
||||||
|
username: client.username
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,28 +299,7 @@ function createClient(options) {
|
||||||
hash.update(packet.publicKey);
|
hash.update(packet.publicKey);
|
||||||
|
|
||||||
var digest = mcHexDigest(hash);
|
var digest = mcHexDigest(hash);
|
||||||
var request = superagent.get("http://session.minecraft.net/game/joinserver.jsp");
|
joinServer(this.username, digest, accessToken, client.session.selectedProfile.id, cb);
|
||||||
request.query({
|
|
||||||
user: client.session.username,
|
|
||||||
sessionId: client.session.id,
|
|
||||||
serverId: digest,
|
|
||||||
});
|
|
||||||
request.end(function(err, resp) {
|
|
||||||
var myErr;
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
} else if (resp.serverError) {
|
|
||||||
myErr = new Error("session.minecraft.net is broken: " + resp.status);
|
|
||||||
myErr.code = 'EMCSESSION500';
|
|
||||||
cb(myErr);
|
|
||||||
} else if (resp.clientError) {
|
|
||||||
myErr = new Error("session.minecraft.net rejected request: " + resp.status + " " + resp.text);
|
|
||||||
myErr.code = 'EMCSESSION400';
|
|
||||||
cb(myErr);
|
|
||||||
} else {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendEncryptionKeyResponse() {
|
function sendEncryptionKeyResponse() {
|
||||||
|
@ -318,19 +308,19 @@ function createClient(options) {
|
||||||
var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
||||||
client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||||
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||||
client.write(0xfc, {
|
client.write(0x01, {
|
||||||
sharedSecret: encryptedSharedSecretBuffer,
|
sharedSecret: encryptedSharedSecretBuffer,
|
||||||
verifyToken: encryptedVerifyTokenBuffer,
|
verifyToken: encryptedVerifyTokenBuffer,
|
||||||
});
|
});
|
||||||
|
client.encryptionEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEncryptionKeyResponse(packet) {
|
function onLogin(packet) {
|
||||||
assert.strictEqual(packet.sharedSecret.length, 0);
|
client.state = states.PLAY;
|
||||||
assert.strictEqual(packet.verifyToken.length, 0);
|
client.uuid = packet.uuid;
|
||||||
client.encryptionEnabled = true;
|
client.username = packet.username;
|
||||||
client.write(0xcd, { payload: 0 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,11 +342,13 @@ function mcHexDigest(hash) {
|
||||||
var buffer = new Buffer(hash.digest(), 'binary');
|
var buffer = new Buffer(hash.digest(), 'binary');
|
||||||
// check for negative hashes
|
// check for negative hashes
|
||||||
var negative = buffer.readInt8(0) < 0;
|
var negative = buffer.readInt8(0) < 0;
|
||||||
if (negative) performTwosCompliment(buffer);
|
if (negative)
|
||||||
|
performTwosCompliment(buffer);
|
||||||
var digest = buffer.toString('hex');
|
var digest = buffer.toString('hex');
|
||||||
// trim leading zeroes
|
// trim leading zeroes
|
||||||
digest = digest.replace(/^0+/g, '');
|
digest = digest.replace(/^0+/g, '');
|
||||||
if (negative) digest = '-' + digest;
|
if (negative)
|
||||||
|
digest = '-' + digest;
|
||||||
return digest;
|
return digest;
|
||||||
|
|
||||||
function performTwosCompliment(buffer) {
|
function performTwosCompliment(buffer) {
|
||||||
|
@ -374,42 +366,3 @@ function mcHexDigest(hash) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ var net = require('net')
|
||||||
, dns = require('dns')
|
, dns = require('dns')
|
||||||
, createPacketBuffer = protocol.createPacketBuffer
|
, createPacketBuffer = protocol.createPacketBuffer
|
||||||
, parsePacket = protocol.parsePacket
|
, parsePacket = protocol.parsePacket
|
||||||
|
, states = protocol.states
|
||||||
, debug = protocol.debug
|
, debug = protocol.debug
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -13,6 +14,17 @@ module.exports = Client;
|
||||||
function Client(isServer) {
|
function Client(isServer) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
this._state = states.HANDSHAKING;
|
||||||
|
Object.defineProperty(this, "state", {
|
||||||
|
get: function() {
|
||||||
|
return this._state;
|
||||||
|
},
|
||||||
|
set: function(newProperty) {
|
||||||
|
var oldProperty = this._state;
|
||||||
|
this._state = newProperty;
|
||||||
|
this.emit('state', newProperty, oldProperty);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.isServer = !!isServer;
|
this.isServer = !!isServer;
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.encryptionEnabled = false;
|
this.encryptionEnabled = false;
|
||||||
|
@ -30,7 +42,7 @@ Client.prototype.setSocket = function(socket) {
|
||||||
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
||||||
var parsed, packet;
|
var parsed, packet;
|
||||||
while (true) {
|
while (true) {
|
||||||
parsed = parsePacket(incomingBuffer, self.isServer);
|
parsed = parsePacket(incomingBuffer, self.state, self.isServer);
|
||||||
if (! parsed) break;
|
if (! parsed) break;
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
this.emit('error', parsed.error);
|
this.emit('error', parsed.error);
|
||||||
|
@ -39,6 +51,7 @@ Client.prototype.setSocket = function(socket) {
|
||||||
}
|
}
|
||||||
packet = parsed.results;
|
packet = parsed.results;
|
||||||
incomingBuffer = incomingBuffer.slice(parsed.size);
|
incomingBuffer = incomingBuffer.slice(parsed.size);
|
||||||
|
self.emit([self.state, packet.id], packet);
|
||||||
self.emit(packet.id, packet);
|
self.emit(packet.id, packet);
|
||||||
self.emit('packet', packet);
|
self.emit('packet', packet);
|
||||||
}
|
}
|
||||||
|
@ -78,7 +91,7 @@ Client.prototype.connect = function(port, host) {
|
||||||
} else {
|
} else {
|
||||||
self.setSocket(net.connect(port, host));
|
self.setSocket(net.connect(port, host));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.setSocket(net.connect(port, host));
|
self.setSocket(net.connect(port, host));
|
||||||
}
|
}
|
||||||
|
@ -90,9 +103,16 @@ Client.prototype.end = function(reason) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.write = function(packetId, params) {
|
Client.prototype.write = function(packetId, params) {
|
||||||
var buffer = createPacketBuffer(packetId, params, this.isServer);
|
if (Array.isArray(packetId)) {
|
||||||
|
if (packetId[0] !== this.state)
|
||||||
|
return false;
|
||||||
|
packetId = packetId[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = createPacketBuffer(packetId, this.state, params, this.isServer);
|
||||||
debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")");
|
debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")");
|
||||||
debug(params);
|
debug(params);
|
||||||
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
|
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
|
||||||
this.socket.write(out);
|
this.socket.write(out);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
56
lib/ping.js
56
lib/ping.js
|
@ -1,6 +1,7 @@
|
||||||
var net = require('net')
|
var net = require('net')
|
||||||
, Client = require('./client')
|
, Client = require('./client')
|
||||||
, protocol = require('./protocol')
|
, protocol = require('./protocol')
|
||||||
|
, states = protocol.states
|
||||||
;
|
;
|
||||||
|
|
||||||
module.exports = ping;
|
module.exports = ping;
|
||||||
|
@ -10,42 +11,35 @@ function ping(options, cb) {
|
||||||
var port = options.port || 25565;
|
var port = options.port || 25565;
|
||||||
|
|
||||||
var client = new Client();
|
var client = new Client();
|
||||||
client.once(0xff, function(packet) {
|
|
||||||
var parts = packet.reason.split('\u0000');
|
|
||||||
var results;
|
|
||||||
try {
|
|
||||||
results = {
|
|
||||||
prefix: parts[0],
|
|
||||||
protocol: parseInt(parts[1], 10),
|
|
||||||
version: parts[2],
|
|
||||||
motd: parts[3],
|
|
||||||
playerCount: parseInt(parts[4], 10),
|
|
||||||
maxPlayers: parseInt(parts[5], 10),
|
|
||||||
latency: Date.now() - start
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
client.end();
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.end();
|
|
||||||
cb(null, results);
|
|
||||||
});
|
|
||||||
client.on('error', function(err) {
|
client.on('error', function(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
client.on('connect', function() {
|
|
||||||
client.write(0xfe, {
|
client.once([states.STATUS, 0x00], function(packet) {
|
||||||
readSuccessfully: 1,
|
var data = JSON.parse(packet.response);
|
||||||
customPayloadId: 250,
|
var start = Date.now();
|
||||||
magicText: "MC|PingHost",
|
client.once(0x01, function(packet) {
|
||||||
len: 3 + host.length + 4,
|
data.latency = Date.now() - start;
|
||||||
version: protocol.version,
|
cb(null, data);
|
||||||
ip: host,
|
client.end();
|
||||||
port: port,
|
|
||||||
});
|
});
|
||||||
|
client.write(0x01, { time: [0, 0]});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('state', function(newState) {
|
||||||
|
if (newState === states.STATUS)
|
||||||
|
client.write(0x00, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('connect', function() {
|
||||||
|
client.write(0x00, {
|
||||||
|
protocolVersion: 4,
|
||||||
|
serverHost: host,
|
||||||
|
serverPort: port,
|
||||||
|
nextState: 1
|
||||||
|
});
|
||||||
|
client.state = states.STATUS;
|
||||||
});
|
});
|
||||||
|
|
||||||
var start = Date.now();
|
|
||||||
client.connect(port, host);
|
client.connect(port, host);
|
||||||
}
|
}
|
||||||
|
|
1322
lib/protocol.js
1322
lib/protocol.js
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,7 @@ var net = require('net')
|
||||||
, util = require('util')
|
, util = require('util')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, Client = require('./client')
|
, Client = require('./client')
|
||||||
|
, states = require('./protocol').states
|
||||||
;
|
;
|
||||||
|
|
||||||
module.exports = Server;
|
module.exports = Server;
|
||||||
|
@ -25,7 +26,11 @@ Server.prototype.listen = function(port, host) {
|
||||||
var client = new Client(true);
|
var client = new Client(true);
|
||||||
client._end = client.end;
|
client._end = client.end;
|
||||||
client.end = function end(endReason) {
|
client.end = function end(endReason) {
|
||||||
client.write(0xff, {reason: endReason});
|
if (client.state === states.PLAY) {
|
||||||
|
client.write(0x40, {reason: endReason});
|
||||||
|
} else if (client.state === states.LOGIN) {
|
||||||
|
client.write(0x00, {reason: endReason});
|
||||||
|
}
|
||||||
client._end(endReason);
|
client._end(endReason);
|
||||||
};
|
};
|
||||||
client.id = nextId++;
|
client.id = nextId++;
|
||||||
|
|
104
lib/yggdrasil.js
Normal file
104
lib/yggdrasil.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
var superagent = require("superagent");
|
||||||
|
|
||||||
|
var loginSrv = "https://authserver.mojang.com";
|
||||||
|
|
||||||
|
function getSession(username, password, clientToken, refresh, cb) {
|
||||||
|
if (refresh) {
|
||||||
|
var accessToken = password;
|
||||||
|
superagent.post(loginSrv + "/refresh")
|
||||||
|
.type("json")
|
||||||
|
.send({
|
||||||
|
"accessToken": accessToken,
|
||||||
|
"clientToken": clientToken
|
||||||
|
})
|
||||||
|
.end(function (resp) {
|
||||||
|
if (resp.ok) {
|
||||||
|
var session = {
|
||||||
|
accessToken: resp.body.accessToken,
|
||||||
|
clientToken: resp.body.clientToken,
|
||||||
|
username: resp.body.selectedProfile.name
|
||||||
|
};
|
||||||
|
cb(null, session);
|
||||||
|
} else {
|
||||||
|
var myErr = new Error(resp.body.error);
|
||||||
|
myErr.errorMessage = resp.body.errorMessage;
|
||||||
|
myErr.cause = resp.body.cause;
|
||||||
|
cb(myErr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
superagent.post(loginSrv + "/authenticate")
|
||||||
|
.type("json")
|
||||||
|
.send({
|
||||||
|
"agent": {
|
||||||
|
"name": "Minecraft",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"clientToken": clientToken
|
||||||
|
})
|
||||||
|
.end(function (resp) {
|
||||||
|
if (resp.ok) {
|
||||||
|
var session = resp.body;
|
||||||
|
session.username = resp.body.selectedProfile.name;
|
||||||
|
cb(null, session);
|
||||||
|
} else {
|
||||||
|
var myErr = new Error(resp.body.error);
|
||||||
|
myErr.errorMessage = resp.body.errorMessage;
|
||||||
|
myErr.cause = resp.body.cause;
|
||||||
|
cb(myErr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinServer(username, serverId, accessToken, selectedProfile, cb) {
|
||||||
|
superagent.post("https://sessionserver.mojang.com/session/minecraft/join")
|
||||||
|
.type("json")
|
||||||
|
.send({
|
||||||
|
"accessToken": accessToken,
|
||||||
|
"selectedProfile": selectedProfile,
|
||||||
|
"serverId": serverId
|
||||||
|
})
|
||||||
|
.end(function(resp) {
|
||||||
|
if (resp.ok) {
|
||||||
|
cb(null);
|
||||||
|
} else {
|
||||||
|
var myErr = new Error(resp.body.error);
|
||||||
|
myErr.errorMessage = resp.body.errorMessage;
|
||||||
|
myErr.cause = resp.body.cause;
|
||||||
|
cb(myErr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSession(username, serverId, cb) {
|
||||||
|
superagent.get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + username + "&serverId=" + serverId)
|
||||||
|
.end(function(resp) {
|
||||||
|
console.log(resp.body);
|
||||||
|
if (resp.ok) {
|
||||||
|
if ("id" in resp.body) {
|
||||||
|
cb(null, resp.body.id);
|
||||||
|
} else {
|
||||||
|
var myErr = new Error("Failed to verify username!");
|
||||||
|
cb(myErr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var myErr = new Error(resp.body.error);
|
||||||
|
myErr.errorMessage = resp.body.errorMessage;
|
||||||
|
myErr.cause = resp.body.cause;
|
||||||
|
cb(myErr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getSession = getSession;
|
||||||
|
exports.joinServer = joinServer;
|
||||||
|
exports.validateSession = validateSession;
|
||||||
|
exports.generateUUID = require("node-uuid").v4;
|
||||||
|
exports.loginType = "yggdrasil";
|
|
@ -35,6 +35,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ursa": "~0.8.0",
|
"ursa": "~0.8.0",
|
||||||
"superagent": "~0.10.0",
|
"superagent": "~0.10.0",
|
||||||
"buffer-equal": "0.0.0"
|
"buffer-equal": "0.0.0",
|
||||||
|
"ansi-color": "0.2.1",
|
||||||
|
"node-uuid": "~1.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ var ITERATIONS = 100000;
|
||||||
|
|
||||||
var Client = require('../lib/client'),
|
var Client = require('../lib/client'),
|
||||||
EventEmitter = require('events').EventEmitter,
|
EventEmitter = require('events').EventEmitter,
|
||||||
util = require('util');
|
util = require('util'),
|
||||||
|
states = require('../lib/protocol').states;
|
||||||
|
|
||||||
var FakeSocket = function() {
|
var FakeSocket = function() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
@ -13,12 +14,12 @@ FakeSocket.prototype.write = function(){};
|
||||||
var client = new Client();
|
var client = new Client();
|
||||||
var socket = new FakeSocket();
|
var socket = new FakeSocket();
|
||||||
client.setSocket(socket);
|
client.setSocket(socket);
|
||||||
|
client.state = states.PLAY;
|
||||||
|
|
||||||
var testData = [
|
var testDataWrite = [
|
||||||
{id: 0x0, params: {keepAliveId: 957759560}},
|
{id: 0x00, params: {keepAliveId: 957759560}},
|
||||||
{id: 0x3, params: {message: '<Bob> Hello World!'}},
|
{id: 0x01, params: {message: '<Bob> Hello World!'}},
|
||||||
{id: 0xd, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
|
{id: 0x06, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
|
||||||
{id: 0xe, params: {status: 1, x: 32, y: 64, z: 32, face: 3}}
|
|
||||||
// TODO: add more packets for better quality data
|
// TODO: add more packets for better quality data
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -26,20 +27,30 @@ var start, i, j;
|
||||||
console.log('Beginning write test');
|
console.log('Beginning write test');
|
||||||
start = Date.now();
|
start = Date.now();
|
||||||
for(i = 0; i < ITERATIONS; i++) {
|
for(i = 0; i < ITERATIONS; i++) {
|
||||||
for(j = 0; j < testData.length; j++) {
|
for(j = 0; j < testDataWrite.length; j++) {
|
||||||
client.write(testData[j].id, testData[j].params);
|
client.write(testDataWrite[j].id, testDataWrite[j].params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Finished write test in ' + (Date.now() - start) / 1000 + ' seconds');
|
console.log('Finished write test in ' + (Date.now() - start) / 1000 + ' seconds');
|
||||||
|
|
||||||
|
var testDataRead = [
|
||||||
|
{id: 0x00, params: {keepAliveId: 957759560}},
|
||||||
|
{id: 0x02, params: {message: '<Bob> Hello World!'}},
|
||||||
|
{id: 0x08, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
|
||||||
|
];
|
||||||
|
|
||||||
|
client.isServer = true;
|
||||||
|
|
||||||
var inputData = new Buffer(0);
|
var inputData = new Buffer(0);
|
||||||
socket.write = function(data) {
|
socket.write = function(data) {
|
||||||
inputData = Buffer.concat([inputData, data]);
|
inputData = Buffer.concat([inputData, data]);
|
||||||
};
|
};
|
||||||
for(i = 0; i < testData.length; i++) {
|
for(i = 0; i < testDataRead.length; i++) {
|
||||||
client.write(testData[i].id, testData[i].params);
|
client.write(testDataRead[i].id, testDataRead[i].params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.isServer = false;
|
||||||
|
|
||||||
console.log('Beginning read test');
|
console.log('Beginning read test');
|
||||||
start = Date.now();
|
start = Date.now();
|
||||||
for(i = 0; i < ITERATIONS; i++) {
|
for(i = 0; i < ITERATIONS; i++) {
|
||||||
|
|
218
test/test.js
218
test/test.js
|
@ -1,5 +1,6 @@
|
||||||
var mc = require('../')
|
var mc = require('../')
|
||||||
, protocol = mc.protocol
|
, protocol = mc.protocol
|
||||||
|
, states = protocol.states
|
||||||
, Client = mc.Client
|
, Client = mc.Client
|
||||||
, Server = mc.Server
|
, Server = mc.Server
|
||||||
, spawn = require('child_process').spawn
|
, spawn = require('child_process').spawn
|
||||||
|
@ -18,26 +19,31 @@ var mc = require('../')
|
||||||
|
|
||||||
var defaultServerProps = {
|
var defaultServerProps = {
|
||||||
'generator-settings': "",
|
'generator-settings': "",
|
||||||
|
'op-permission-level': '4',
|
||||||
'allow-nether': 'true',
|
'allow-nether': 'true',
|
||||||
'level-name': 'world',
|
'level-name': 'world',
|
||||||
'enable-query': 'false',
|
'enable-query': 'false',
|
||||||
'allow-flight': 'false',
|
'allow-flight': 'false',
|
||||||
|
'announce-player-achievements': true,
|
||||||
'server-port': '25565',
|
'server-port': '25565',
|
||||||
'level-type': 'DEFAULT',
|
'level-type': 'DEFAULT',
|
||||||
'enable-rcon': 'false',
|
'enable-rcon': 'false',
|
||||||
|
'force-gamemode': 'false',
|
||||||
'level-seed': "",
|
'level-seed': "",
|
||||||
'server-ip': "",
|
'server-ip': "",
|
||||||
'max-build-height': '256',
|
'max-build-height': '256',
|
||||||
'spawn-npcs': 'true',
|
'spawn-npcs': 'true',
|
||||||
'white-list': 'false',
|
'white-list': 'false',
|
||||||
'spawn-animals': 'true',
|
'spawn-animals': 'true',
|
||||||
'snooper-enabled': 'true',
|
|
||||||
'hardcore': 'false',
|
'hardcore': 'false',
|
||||||
'texture-pack': '',
|
'snooper-enabled': 'true',
|
||||||
'online-mode': 'true',
|
'online-mode': 'true',
|
||||||
|
'resource-pack': '',
|
||||||
'pvp': 'true',
|
'pvp': 'true',
|
||||||
'difficulty': '1',
|
'difficulty': '1',
|
||||||
|
'enable-command-block': 'false',
|
||||||
'gamemode': '0',
|
'gamemode': '0',
|
||||||
|
'player-idle-timeout': '0',
|
||||||
'max-players': '20',
|
'max-players': '20',
|
||||||
'spawn-monsters': 'true',
|
'spawn-monsters': 'true',
|
||||||
'generate-structures': 'true',
|
'generate-structures': 'true',
|
||||||
|
@ -50,6 +56,7 @@ var values = {
|
||||||
'int': 123456,
|
'int': 123456,
|
||||||
'short': -123,
|
'short': -123,
|
||||||
'ushort': 123,
|
'ushort': 123,
|
||||||
|
'varint': 25992,
|
||||||
'byte': -10,
|
'byte': -10,
|
||||||
'ubyte': 8,
|
'ubyte': 8,
|
||||||
'string': "hi hi this is my client string",
|
'string': "hi hi this is my client string",
|
||||||
|
@ -102,7 +109,9 @@ var values = {
|
||||||
'intArray8': [1, 2, 3, 4],
|
'intArray8': [1, 2, 3, 4],
|
||||||
'intVector': {x: 1, y: 2, z: 3},
|
'intVector': {x: 1, y: 2, z: 3},
|
||||||
'byteVector': {x: 1, y: 2, z: 3},
|
'byteVector': {x: 1, y: 2, z: 3},
|
||||||
'byteVectorArray': [{x: 1, y: 2, z: 3}]
|
'byteVectorArray': [{x: 1, y: 2, z: 3}],
|
||||||
|
'statisticArray': {"stuff": 13, "anotherstuff": 6392},
|
||||||
|
'matchArray': ["hallo", "heya"]
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("packets", function() {
|
describe("packets", function() {
|
||||||
|
@ -127,45 +136,51 @@ describe("packets", function() {
|
||||||
client.end();
|
client.end();
|
||||||
});
|
});
|
||||||
var packetId, packetInfo, field;
|
var packetId, packetInfo, field;
|
||||||
for(packetId in protocol.packets) {
|
for(state in protocol.packets) {
|
||||||
if (!protocol.packets.hasOwnProperty(packetId)) continue;
|
if (!protocol.packets.hasOwnProperty(state)) continue;
|
||||||
|
for(packetId in protocol.packets[state].toServer) {
|
||||||
packetId = parseInt(packetId, 10);
|
if (!protocol.packets[state].toServer.hasOwnProperty(packetId)) continue;
|
||||||
packetInfo = protocol.packets[packetId];
|
packetId = parseInt(packetId, 10);
|
||||||
it("0x" + zfill(parseInt(packetId, 10).toString(16), 2),
|
packetInfo = protocol.get(packetId, state, true);
|
||||||
callTestPacket(packetId, packetInfo));
|
it(state + ",ServerBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
|
||||||
|
callTestPacket(packetId, packetInfo, state, true));
|
||||||
|
}
|
||||||
|
for(packetId in protocol.packets[state].toClient) {
|
||||||
|
if (!protocol.packets[state].toClient.hasOwnProperty(packetId)) continue;
|
||||||
|
packetId = parseInt(packetId, 10);
|
||||||
|
packetInfo = protocol.get(packetId, state, false);
|
||||||
|
it(state + ",ClientBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
|
||||||
|
callTestPacket(packetId, packetInfo, state, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function callTestPacket(packetId, packetInfo) {
|
function callTestPacket(packetId, packetInfo, state, toServer) {
|
||||||
return function(done) {
|
return function(done) {
|
||||||
var batch = new Batch();
|
client.state = state;
|
||||||
batch.push(function(done) {
|
serverClient.state = state;
|
||||||
testPacket(packetId, protocol.get(packetId, false), done);
|
testPacket(packetId, packetInfo, state, toServer, done);
|
||||||
});
|
};
|
||||||
batch.push(function(done) {
|
|
||||||
testPacket(packetId, protocol.get(packetId, true), done);
|
|
||||||
});
|
|
||||||
batch.end(function(err, results) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
function testPacket(packetId, packetInfo, done) {
|
function testPacket(packetId, packetInfo, state, toServer, done) {
|
||||||
// empty object uses default values
|
// empty object uses default values
|
||||||
var packet = {};
|
var packet = {};
|
||||||
packetInfo.forEach(function(field) {
|
packetInfo.forEach(function(field) {
|
||||||
packet[field.name] = values[field.type];
|
packet[field.name] = values[field.type];
|
||||||
});
|
});
|
||||||
serverClient.once(packetId, function(receivedPacket) {
|
if (toServer) {
|
||||||
delete receivedPacket.id;
|
serverClient.once([state, packetId], function(receivedPacket) {
|
||||||
assertPacketsMatch(packet, receivedPacket);
|
delete receivedPacket.id;
|
||||||
client.once(packetId, function(clientReceivedPacket) {
|
assertPacketsMatch(packet, receivedPacket);
|
||||||
delete clientReceivedPacket.id;
|
|
||||||
assertPacketsMatch(receivedPacket, clientReceivedPacket);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
serverClient.write(packetId, receivedPacket);
|
client.write(packetId, packet);
|
||||||
});
|
} else {
|
||||||
client.write(packetId, packet);
|
client.once([state, packetId], function(receivedPacket) {
|
||||||
|
delete receivedPacket.id;
|
||||||
|
assertPacketsMatch(packet, receivedPacket);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
serverClient.write(packetId, packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function assertPacketsMatch(p1, p2) {
|
function assertPacketsMatch(p1, p2) {
|
||||||
packetInfo.forEach(function(field) {
|
packetInfo.forEach(function(field) {
|
||||||
|
@ -182,7 +197,7 @@ describe("packets", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("client", function() {
|
describe("client", function() {
|
||||||
this.timeout(20000);
|
this.timeout(40000);
|
||||||
|
|
||||||
var mcServer;
|
var mcServer;
|
||||||
function startServer(propOverrides, done) {
|
function startServer(propOverrides, done) {
|
||||||
|
@ -238,7 +253,7 @@ describe("client", function() {
|
||||||
//console.error("[MC]", line);
|
//console.error("[MC]", line);
|
||||||
});
|
});
|
||||||
function onLine(line) {
|
function onLine(line) {
|
||||||
if (/\[INFO\] Done/.test(line)) {
|
if (/\[Server thread\/INFO\]: Done/.test(line)) {
|
||||||
mcServer.removeListener('line', onLine);
|
mcServer.removeListener('line', onLine);
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@ -265,14 +280,14 @@ describe("client", function() {
|
||||||
assert.ok(results.latency >= 0);
|
assert.ok(results.latency >= 0);
|
||||||
assert.ok(results.latency <= 1000);
|
assert.ok(results.latency <= 1000);
|
||||||
delete results.latency;
|
delete results.latency;
|
||||||
assert.deepEqual(results, {
|
delete results.favicon; // too lazy to figure it out
|
||||||
prefix: "§1",
|
/* assert.deepEqual(results, {
|
||||||
protocol: protocol.version,
|
version: {
|
||||||
version: protocol.minecraftVersion,
|
name: '1.7.4',
|
||||||
motd: 'test1234',
|
protocol: 4
|
||||||
playerCount: 0,
|
},
|
||||||
maxPlayers: 120
|
description: { text: "test1234" }
|
||||||
});
|
});*/
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -284,34 +299,42 @@ describe("client", function() {
|
||||||
password: process.env.MC_PASSWORD,
|
password: process.env.MC_PASSWORD,
|
||||||
});
|
});
|
||||||
mcServer.on('line', function(line) {
|
mcServer.on('line', function(line) {
|
||||||
var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
|
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
|
||||||
if (! match) return;
|
if (! match) return;
|
||||||
assert.strictEqual(match[1], client.session.username);
|
assert.strictEqual(match[1], client.session.username);
|
||||||
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
||||||
mcServer.stdin.write("say hello\n");
|
mcServer.stdin.write("say hello\n");
|
||||||
});
|
});
|
||||||
var chatCount = 0;
|
var chatCount = 0;
|
||||||
client.on(0x01, function(packet) {
|
client.on([states.PLAY, 0x01], function(packet) {
|
||||||
assert.strictEqual(packet.levelType, 'default');
|
assert.strictEqual(packet.levelType, 'default');
|
||||||
assert.strictEqual(packet.difficulty, 1);
|
assert.strictEqual(packet.difficulty, 1);
|
||||||
assert.strictEqual(packet.dimension, 0);
|
assert.strictEqual(packet.dimension, 0);
|
||||||
assert.strictEqual(packet.gameMode, 0);
|
assert.strictEqual(packet.gameMode, 0);
|
||||||
client.write(0x03, {
|
client.write(0x01, {
|
||||||
message: "hello everyone; I have logged in."
|
message: "hello everyone; I have logged in."
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
client.on(0x03, function(packet) {
|
client.on([states.PLAY, 0x02], function(packet) {
|
||||||
chatCount += 1;
|
chatCount += 1;
|
||||||
assert.ok(chatCount <= 2);
|
assert.ok(chatCount <= 2);
|
||||||
var message = JSON.parse(packet.message);
|
var message = JSON.parse(packet.message);
|
||||||
if (chatCount === 1) {
|
if (chatCount === 1) {
|
||||||
assert.strictEqual(message.translate, "chat.type.text");
|
assert.strictEqual(message.translate, "chat.type.text");
|
||||||
assert.strictEqual(message.using[0], client.session.username);
|
assert.deepEqual(message["with"][0], {
|
||||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
clickEvent: {
|
||||||
|
action: "suggest_command",
|
||||||
|
value: "/msg " + client.session.username + " "
|
||||||
|
},
|
||||||
|
text: client.session.username
|
||||||
|
});
|
||||||
|
assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
|
||||||
} else if (chatCount === 2) {
|
} else if (chatCount === 2) {
|
||||||
assert.strictEqual(message.translate, "chat.type.announcement");
|
assert.strictEqual(message.translate, "chat.type.announcement");
|
||||||
assert.strictEqual(message.using[0], "Server");
|
assert.strictEqual(message["with"][0], "Server");
|
||||||
assert.strictEqual(message.using[1], "hello");
|
assert.deepEqual(message["with"][1], { text: "",
|
||||||
|
extra: ["hello"]
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -323,34 +346,42 @@ describe("client", function() {
|
||||||
username: 'Player',
|
username: 'Player',
|
||||||
});
|
});
|
||||||
mcServer.on('line', function(line) {
|
mcServer.on('line', function(line) {
|
||||||
var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
|
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
|
||||||
if (! match) return;
|
if (! match) return;
|
||||||
assert.strictEqual(match[1], 'Player');
|
assert.strictEqual(match[1], 'Player');
|
||||||
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
||||||
mcServer.stdin.write("say hello\n");
|
mcServer.stdin.write("say hello\n");
|
||||||
});
|
});
|
||||||
var chatCount = 0;
|
var chatCount = 0;
|
||||||
client.on(0x01, function(packet) {
|
client.on([states.PLAY, 0x01], function(packet) {
|
||||||
assert.strictEqual(packet.levelType, 'default');
|
assert.strictEqual(packet.levelType, 'default');
|
||||||
assert.strictEqual(packet.difficulty, 1);
|
assert.strictEqual(packet.difficulty, 1);
|
||||||
assert.strictEqual(packet.dimension, 0);
|
assert.strictEqual(packet.dimension, 0);
|
||||||
assert.strictEqual(packet.gameMode, 0);
|
assert.strictEqual(packet.gameMode, 0);
|
||||||
client.write(0x03, {
|
client.write(0x01, {
|
||||||
message: "hello everyone; I have logged in."
|
message: "hello everyone; I have logged in."
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
client.on(0x03, function(packet) {
|
client.on([states.PLAY, 0x02], function(packet) {
|
||||||
chatCount += 1;
|
chatCount += 1;
|
||||||
assert.ok(chatCount <= 2);
|
assert.ok(chatCount <= 2);
|
||||||
var message = JSON.parse(packet.message);
|
var message = JSON.parse(packet.message);
|
||||||
if (chatCount === 1) {
|
if (chatCount === 1) {
|
||||||
assert.strictEqual(message.translate, "chat.type.text");
|
assert.strictEqual(message.translate, "chat.type.text");
|
||||||
assert.strictEqual(message.using[0], "Player");
|
assert.deepEqual(message["with"][0], {
|
||||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
clickEvent: {
|
||||||
|
action: "suggest_command",
|
||||||
|
value: "/msg Player "
|
||||||
|
},
|
||||||
|
text: "Player"
|
||||||
|
});
|
||||||
|
assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
|
||||||
} else if (chatCount === 2) {
|
} else if (chatCount === 2) {
|
||||||
assert.strictEqual(message.translate, "chat.type.announcement");
|
assert.strictEqual(message.translate, "chat.type.announcement");
|
||||||
assert.strictEqual(message.using[0], "Server");
|
assert.strictEqual(message["with"][0], "Server");
|
||||||
assert.strictEqual(message.using[1], "hello");
|
assert.deepEqual(message["with"][1], { text: "",
|
||||||
|
extra: ["hello"]
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -362,8 +393,8 @@ describe("client", function() {
|
||||||
username: 'Player',
|
username: 'Player',
|
||||||
});
|
});
|
||||||
var gotKicked = false;
|
var gotKicked = false;
|
||||||
client.on(0xff, function(packet) {
|
client.on([states.LOGIN, 0x00], function(packet) {
|
||||||
assert.strictEqual(packet.reason, "Failed to verify username!");
|
assert.strictEqual(packet.reason, '"Failed to verify username!"');
|
||||||
gotKicked = true;
|
gotKicked = true;
|
||||||
});
|
});
|
||||||
client.on('end', function() {
|
client.on('end', function() {
|
||||||
|
@ -377,23 +408,26 @@ describe("client", function() {
|
||||||
var client = mc.createClient({
|
var client = mc.createClient({
|
||||||
username: 'Player',
|
username: 'Player',
|
||||||
});
|
});
|
||||||
client.on(0x01, function(packet) {
|
client.on([states.PLAY, 0x01], function(packet) {
|
||||||
client.write(0x03, {
|
client.write(0x01, {
|
||||||
message: "hello everyone; I have logged in."
|
message: "hello everyone; I have logged in."
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
client.on(0x03, function(packet) {
|
client.on([states.PLAY, 0x02], function(packet) {
|
||||||
var message = JSON.parse(packet.message);
|
var message = JSON.parse(packet.message);
|
||||||
assert.strictEqual(message.translate, "chat.type.text");
|
assert.strictEqual(message.translate, "chat.type.text");
|
||||||
assert.strictEqual(message.using[0], "Player");
|
assert.deepEqual(message["with"][0], {
|
||||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
clickEvent: {
|
||||||
|
action: "suggest_command",
|
||||||
|
value: "/msg Player "
|
||||||
|
},
|
||||||
|
text: "Player"
|
||||||
|
});
|
||||||
|
assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
done();
|
done();
|
||||||
}, SURVIVE_TIME);
|
}, SURVIVE_TIME);
|
||||||
});
|
});
|
||||||
client.on(0x0d, function(packet) {
|
|
||||||
assert.ok(packet.stance > packet.y, "stance should be > y");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -482,12 +516,16 @@ describe("mc-server", function() {
|
||||||
assert.ok(results.latency <= 1000);
|
assert.ok(results.latency <= 1000);
|
||||||
delete results.latency;
|
delete results.latency;
|
||||||
assert.deepEqual(results, {
|
assert.deepEqual(results, {
|
||||||
prefix: "§1",
|
version: {
|
||||||
protocol: protocol.version,
|
name: "1.7.2",
|
||||||
version: protocol.minecraftVersion,
|
protocol: 4
|
||||||
motd: 'test1234',
|
},
|
||||||
playerCount: 0,
|
players: {
|
||||||
maxPlayers: 120
|
max: 120,
|
||||||
|
online: 0,
|
||||||
|
sample: []
|
||||||
|
},
|
||||||
|
description: { text: "test1234" }
|
||||||
});
|
});
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
@ -514,7 +552,7 @@ describe("mc-server", function() {
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
maxPlayers: server.maxPlayers
|
maxPlayers: server.maxPlayers
|
||||||
});
|
});
|
||||||
client.on(0x03, function(packet) {
|
client.on([states.PLAY, 0x01], function(packet) {
|
||||||
var message = '<' + client.username + '>' + ' ' + packet.message;
|
var message = '<' + client.username + '>' + ' ' + packet.message;
|
||||||
broadcast(message);
|
broadcast(message);
|
||||||
});
|
});
|
||||||
|
@ -522,31 +560,31 @@ describe("mc-server", function() {
|
||||||
server.on('close', done);
|
server.on('close', done);
|
||||||
server.on('listening', function() {
|
server.on('listening', function() {
|
||||||
var player1 = mc.createClient({ username: 'player1' });
|
var player1 = mc.createClient({ username: 'player1' });
|
||||||
player1.on(0x01, function(packet) {
|
player1.on([states.PLAY, 0x01], function(packet) {
|
||||||
assert.strictEqual(packet.gameMode, 1);
|
assert.strictEqual(packet.gameMode, 1);
|
||||||
assert.strictEqual(packet.levelType, 'default');
|
assert.strictEqual(packet.levelType, 'default');
|
||||||
assert.strictEqual(packet.dimension, 0);
|
assert.strictEqual(packet.dimension, 0);
|
||||||
assert.strictEqual(packet.difficulty, 2);
|
assert.strictEqual(packet.difficulty, 2);
|
||||||
player1.once(0x03, function(packet) {
|
player1.once(0x02, function(packet) {
|
||||||
assert.strictEqual(packet.message, 'player2 joined the game.');
|
assert.strictEqual(packet.message, '{"text":"player2 joined the game."}');
|
||||||
player1.once(0x03, function(packet) {
|
player1.once(0x02, function(packet) {
|
||||||
assert.strictEqual(packet.message, '<player2> hi');
|
assert.strictEqual(packet.message, '{"text":"<player2> hi"}');
|
||||||
player2.once(0x03, fn);
|
player2.once(0x02, fn);
|
||||||
function fn(packet) {
|
function fn(packet) {
|
||||||
if (/^<player2>/.test(packet.message)) {
|
if (/<player2>/.test(packet.message)) {
|
||||||
player2.once(0x03, fn);
|
player2.once(0x02, fn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert.strictEqual(packet.message, '<player1> hello');
|
assert.strictEqual(packet.message, '{"text":"<player1> hello"}');
|
||||||
player1.once(0x03, function(packet) {
|
player1.once(0x02, function(packet) {
|
||||||
assert.strictEqual(packet.message, 'player2 left the game.');
|
assert.strictEqual(packet.message, '{"text":"player2 left the game."}');
|
||||||
player1.end();
|
player1.end();
|
||||||
});
|
});
|
||||||
player2.end();
|
player2.end();
|
||||||
}
|
}
|
||||||
player1.write(0x03, { message: "hello" } );
|
player1.write(0x01, { message: "hello" } );
|
||||||
});
|
});
|
||||||
player2.write(0x03, { message: "hi" } );
|
player2.write(0x01, { message: "hi" } );
|
||||||
});
|
});
|
||||||
var player2 = mc.createClient({ username: 'player2' });
|
var player2 = mc.createClient({ username: 'player2' });
|
||||||
});
|
});
|
||||||
|
@ -558,7 +596,7 @@ describe("mc-server", function() {
|
||||||
if (!server.clients.hasOwnProperty(clientId)) continue;
|
if (!server.clients.hasOwnProperty(clientId)) continue;
|
||||||
|
|
||||||
client = server.clients[clientId];
|
client = server.clients[clientId];
|
||||||
if (client !== exclude) client.write(0x03, { message: message });
|
if (client !== exclude) client.write(0x02, { message: JSON.stringify({text: message})});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -610,7 +648,7 @@ describe("mc-server", function() {
|
||||||
});
|
});
|
||||||
server.on('listening', function() {
|
server.on('listening', function() {
|
||||||
var client = mc.createClient({ username: 'lalalal', });
|
var client = mc.createClient({ username: 'lalalal', });
|
||||||
client.on(0x01, function() {
|
client.on([states.PLAY, 0x01], function() {
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue