mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2024-11-14 19:04:59 -05:00
Protocol 1.7 support, Yggdrasil login support, new Client State API
This commit is contained in:
parent
2b594399ea
commit
875d10ed0b
12 changed files with 1189 additions and 858 deletions
|
@ -1,4 +1,6 @@
|
|||
var mc = require('../');
|
||||
var mc = require('../')
|
||||
, states = mc.protocol.states
|
||||
|
||||
var client = mc.createClient({
|
||||
username: process.env.MC_USERNAME,
|
||||
password: process.env.MC_PASSWORD,
|
||||
|
@ -6,12 +8,12 @@ var client = mc.createClient({
|
|||
client.on('connect', function() {
|
||||
console.info('connected');
|
||||
});
|
||||
client.on(0x03, function(packet) {
|
||||
client.on([states.PLAY, 0x02], function(packet) {
|
||||
var jsonMsg = JSON.parse(packet.message);
|
||||
if (jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') {
|
||||
var username = jsonMsg.using[0];
|
||||
var msg = jsonMsg.using[1];
|
||||
var username = jsonMsg.with[0];
|
||||
var msg = jsonMsg.with[1];
|
||||
if (username === client.username) return;
|
||||
client.write(0x03, {message: msg});
|
||||
client.write(0x01, {message: msg});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
var mc = require('../');
|
||||
var states = mc.protocol.states;
|
||||
|
||||
var yellow = '§e';
|
||||
|
||||
|
@ -30,17 +31,16 @@ server.on('login', function(client) {
|
|||
difficulty: 2,
|
||||
maxPlayers: server.maxPlayers
|
||||
});
|
||||
client.write(0x0d, {
|
||||
client.write(0x08, {
|
||||
x: 0,
|
||||
y: 256,
|
||||
stance: 255,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
onGround: true
|
||||
});
|
||||
|
||||
client.on(0x03, function(data) {
|
||||
client.on([states.PLAY, 0x01], function(data) {
|
||||
var message = '<'+client.username+'>' + ' ' + data.message;
|
||||
broadcast(message, client, client.username);
|
||||
console.log(message);
|
||||
|
@ -66,12 +66,12 @@ function broadcast(message, exclude, username) {
|
|||
if (client !== exclude) {
|
||||
var msg = {
|
||||
translate: translate,
|
||||
using: [
|
||||
"with": [
|
||||
username,
|
||||
'Hello, world!'
|
||||
]
|
||||
};
|
||||
client.write(0x03, { message: JSON.stringify(msg) });
|
||||
client.write(0x02, { message: JSON.stringify(msg) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var mc = require('../');
|
||||
|
||||
var options = {
|
||||
'online-mode': false, // optional
|
||||
// 'online-mode': false, // optional
|
||||
};
|
||||
|
||||
var server = mc.createServer(options);
|
||||
|
@ -23,10 +23,9 @@ server.on('login', function(client) {
|
|||
difficulty: 2,
|
||||
maxPlayers: server.maxPlayers
|
||||
});
|
||||
client.write(0x0d, {
|
||||
client.write(0x08, {
|
||||
x: 0,
|
||||
y: 1.62,
|
||||
stance: 0,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
|
@ -35,12 +34,12 @@ server.on('login', function(client) {
|
|||
|
||||
var msg = {
|
||||
translate: 'chat.type.announcement',
|
||||
using: [
|
||||
"with": [
|
||||
'Server',
|
||||
'Hello, world!'
|
||||
]
|
||||
};
|
||||
client.write(0x03, { message: JSON.stringify(msg) });
|
||||
client.write(0x02, { message: JSON.stringify(msg) });
|
||||
});
|
||||
|
||||
server.on('error', function(error) {
|
||||
|
|
247
index.js
247
index.js
|
@ -1,15 +1,20 @@
|
|||
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')
|
||||
, debug = protocol.debug
|
||||
;
|
||||
, 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')
|
||||
, Yggdrasil = require('./lib/yggdrasil.js')
|
||||
, getSession = Yggdrasil.getSession
|
||||
, validateSession = Yggdrasil.validateSession
|
||||
, joinServer = Yggdrasil.joinServer
|
||||
, states = protocol.states
|
||||
, debug = protocol.debug
|
||||
;
|
||||
|
||||
module.exports = {
|
||||
createClient: createClient,
|
||||
|
@ -23,10 +28,10 @@ module.exports = {
|
|||
function createServer(options) {
|
||||
options = options || {};
|
||||
var port = options.port != null ?
|
||||
options.port :
|
||||
options['server-port'] != null ?
|
||||
options['server-port'] :
|
||||
25565 ;
|
||||
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;
|
||||
|
@ -41,9 +46,9 @@ function createServer(options) {
|
|||
server.playerCount = 0;
|
||||
server.onlineModeExceptions = {};
|
||||
server.on("connection", function(client) {
|
||||
client.once(0xfe, onPing);
|
||||
client.once(0x02, onHandshake);
|
||||
client.once(0xFC, onEncryptionKeyResponse);
|
||||
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;
|
||||
|
@ -60,7 +65,8 @@ function createServer(options) {
|
|||
}
|
||||
|
||||
function keepAliveLoop() {
|
||||
if (! keepAlive) return;
|
||||
if (!keepAlive)
|
||||
return;
|
||||
|
||||
// check if the last keepAlive was too long ago (kickTimeout)
|
||||
var elapsed = new Date() - lastKeepAlive;
|
||||
|
@ -90,40 +96,44 @@ function createServer(options) {
|
|||
}
|
||||
|
||||
function onPing(packet) {
|
||||
if (loggedIn) return;
|
||||
client.write(0xff, {
|
||||
reason: [
|
||||
'§1',
|
||||
protocol.version,
|
||||
protocol.minecraftVersion,
|
||||
server.motd,
|
||||
server.playerCount,
|
||||
server.maxPlayers,
|
||||
].join('\u0000')
|
||||
var response = {
|
||||
"version": {
|
||||
"name": protocol.minecraftVersion,
|
||||
"protocol": protocol.version
|
||||
},
|
||||
"players": {
|
||||
"max": server.maxPlayers,
|
||||
"online": server.playerCount,
|
||||
"sample": []
|
||||
},
|
||||
"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;
|
||||
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) {
|
||||
if (encryptionEnabled || 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++) {
|
||||
for (var i = 1; i < publicKeyStrArr.length - 2; i++) {
|
||||
publicKeyStr += publicKeyStrArr[i]
|
||||
}
|
||||
client.publicKey = new Buffer(publicKeyStr,'base64');
|
||||
client.publicKey = new Buffer(publicKeyStr, 'base64');
|
||||
hash = crypto.createHash("sha1");
|
||||
hash.update(serverId);
|
||||
client.write(0xFD, {
|
||||
client.once([states.LOGIN, 0x01], onEncryptionKeyResponse);
|
||||
client.write(0x01, {
|
||||
serverId: serverId,
|
||||
publicKey: client.publicKey,
|
||||
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) {
|
||||
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');
|
||||
return;
|
||||
}
|
||||
|
@ -144,49 +162,29 @@ function createServer(options) {
|
|||
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||
hash.update(sharedSecret);
|
||||
hash.update(client.publicKey);
|
||||
client.write(0xFC, {
|
||||
sharedSecret: new Buffer(0),
|
||||
verifyToken: new Buffer(0)
|
||||
});
|
||||
client.encryptionEnabled = true;
|
||||
|
||||
var isException = !!server.onlineModeExceptions[client.username.toLowerCase()];
|
||||
var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException);
|
||||
var needToVerify = (onlineMode && !isException) || (!onlineMode && isException);
|
||||
var nextStep = needToVerify ? verifyUsername : loginClient;
|
||||
nextStep();
|
||||
|
||||
function verifyUsername() {
|
||||
var digest = mcHexDigest(hash);
|
||||
var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp");
|
||||
request.query({
|
||||
user: client.username,
|
||||
serverId: digest
|
||||
});
|
||||
request.end(function(err, resp) {
|
||||
var myErr;
|
||||
validateSession(client.username, digest, function(err, uuid) {
|
||||
if (err) {
|
||||
server.emit('error', err);
|
||||
client.end('McSessionUnavailable');
|
||||
} 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.end("Failed to verify username!");
|
||||
return;
|
||||
}
|
||||
client.UUID = uuid;
|
||||
loginClient();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loginClient() {
|
||||
client.write(0x02, {uuid: (client.UUID | 0).toString(10), username: client.username});
|
||||
client.state = states.PLAY;
|
||||
loggedIn = true;
|
||||
startKeepAlive();
|
||||
|
||||
|
@ -208,28 +206,36 @@ 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;
|
||||
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(0x00, onKeepAlive);
|
||||
client.once(0xFC, onEncryptionKeyResponse);
|
||||
client.once(0xFD, onEncryptionKeyRequest);
|
||||
if (keepAlive) client.on([states.PLAY, 0x00], onKeepAlive);
|
||||
client.once([states.LOGIN, 0x01], onEncryptionKeyRequest);
|
||||
client.once([states.LOGIN, 0x02], onLogin);
|
||||
|
||||
if (haveCredentials) {
|
||||
// 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) {
|
||||
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, options.clientToken, true, cb);
|
||||
else getSession(options.username, options.password, options.clientToken, false, cb);
|
||||
} else {
|
||||
// assume the server is in offline mode and just go for it.
|
||||
client.username = options.username;
|
||||
|
@ -239,11 +245,16 @@ function createClient(options) {
|
|||
return client;
|
||||
|
||||
function onConnect() {
|
||||
client.write(0x02, {
|
||||
client.write(0x00, {
|
||||
protocolVersion: protocol.version,
|
||||
username: client.username,
|
||||
serverHost: host,
|
||||
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);
|
||||
|
||||
var digest = mcHexDigest(hash);
|
||||
var request = superagent.get("http://session.minecraft.net/game/joinserver.jsp");
|
||||
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();
|
||||
}
|
||||
});
|
||||
joinServer(this.username, digest, accessToken, client.session.selectedProfile.id, cb);
|
||||
}
|
||||
|
||||
function sendEncryptionKeyResponse() {
|
||||
|
@ -318,19 +308,19 @@ function createClient(options) {
|
|||
var encryptedVerifyTokenBuffer = pubKey.encrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING);
|
||||
client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||
client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
|
||||
client.write(0xfc, {
|
||||
client.write(0x01, {
|
||||
sharedSecret: encryptedSharedSecretBuffer,
|
||||
verifyToken: encryptedVerifyTokenBuffer,
|
||||
});
|
||||
client.encryptionEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onEncryptionKeyResponse(packet) {
|
||||
assert.strictEqual(packet.sharedSecret.length, 0);
|
||||
assert.strictEqual(packet.verifyToken.length, 0);
|
||||
client.encryptionEnabled = true;
|
||||
client.write(0xcd, { payload: 0 });
|
||||
|
||||
function onLogin(packet) {
|
||||
client.state = states.PLAY;
|
||||
client.uuid = packet.uuid;
|
||||
client.username = packet.username;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,11 +342,13 @@ function mcHexDigest(hash) {
|
|||
var buffer = new Buffer(hash.digest(), 'binary');
|
||||
// check for negative hashes
|
||||
var negative = buffer.readInt8(0) < 0;
|
||||
if (negative) performTwosCompliment(buffer);
|
||||
if (negative)
|
||||
performTwosCompliment(buffer);
|
||||
var digest = buffer.toString('hex');
|
||||
// trim leading zeroes
|
||||
digest = digest.replace(/^0+/g, '');
|
||||
if (negative) digest = '-' + digest;
|
||||
if (negative)
|
||||
digest = '-' + digest;
|
||||
return digest;
|
||||
|
||||
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')
|
||||
, createPacketBuffer = protocol.createPacketBuffer
|
||||
, parsePacket = protocol.parsePacket
|
||||
, states = protocol.states
|
||||
, debug = protocol.debug
|
||||
;
|
||||
|
||||
|
@ -13,6 +14,17 @@ module.exports = Client;
|
|||
function Client(isServer) {
|
||||
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.socket = null;
|
||||
this.encryptionEnabled = false;
|
||||
|
@ -30,7 +42,7 @@ Client.prototype.setSocket = function(socket) {
|
|||
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
||||
var parsed, packet;
|
||||
while (true) {
|
||||
parsed = parsePacket(incomingBuffer, self.isServer);
|
||||
parsed = parsePacket(incomingBuffer, self.state, self.isServer);
|
||||
if (! parsed) break;
|
||||
if (parsed.error) {
|
||||
this.emit('error', parsed.error);
|
||||
|
@ -39,6 +51,7 @@ Client.prototype.setSocket = function(socket) {
|
|||
}
|
||||
packet = parsed.results;
|
||||
incomingBuffer = incomingBuffer.slice(parsed.size);
|
||||
self.emit([self.state, packet.id], packet);
|
||||
self.emit(packet.id, packet);
|
||||
self.emit('packet', packet);
|
||||
}
|
||||
|
@ -78,7 +91,7 @@ Client.prototype.connect = function(port, host) {
|
|||
} else {
|
||||
self.setSocket(net.connect(port, host));
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.setSocket(net.connect(port, host));
|
||||
}
|
||||
|
@ -90,9 +103,16 @@ Client.prototype.end = function(reason) {
|
|||
};
|
||||
|
||||
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(params);
|
||||
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
|
||||
this.socket.write(out);
|
||||
return true;
|
||||
};
|
||||
|
|
56
lib/ping.js
56
lib/ping.js
|
@ -1,6 +1,7 @@
|
|||
var net = require('net')
|
||||
, Client = require('./client')
|
||||
, protocol = require('./protocol')
|
||||
, states = protocol.states
|
||||
;
|
||||
|
||||
module.exports = ping;
|
||||
|
@ -10,42 +11,35 @@ function ping(options, cb) {
|
|||
var port = options.port || 25565;
|
||||
|
||||
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) {
|
||||
cb(err);
|
||||
});
|
||||
client.on('connect', function() {
|
||||
client.write(0xfe, {
|
||||
readSuccessfully: 1,
|
||||
customPayloadId: 250,
|
||||
magicText: "MC|PingHost",
|
||||
len: 3 + host.length + 4,
|
||||
version: protocol.version,
|
||||
ip: host,
|
||||
port: port,
|
||||
|
||||
client.once([states.STATUS, 0x00], function(packet) {
|
||||
var data = JSON.parse(packet.response);
|
||||
var start = Date.now();
|
||||
client.once(0x01, function(packet) {
|
||||
data.latency = Date.now() - start;
|
||||
cb(null, data);
|
||||
client.end();
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
|
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')
|
||||
, assert = require('assert')
|
||||
, Client = require('./client')
|
||||
, states = require('./protocol').states
|
||||
;
|
||||
|
||||
module.exports = Server;
|
||||
|
@ -25,7 +26,11 @@ Server.prototype.listen = function(port, host) {
|
|||
var client = new Client(true);
|
||||
client._end = client.end;
|
||||
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.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,7 @@
|
|||
"dependencies": {
|
||||
"ursa": "~0.8.0",
|
||||
"superagent": "~0.10.0",
|
||||
"buffer-equal": "0.0.0"
|
||||
"buffer-equal": "0.0.0",
|
||||
"node-uuid": "~1.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ var ITERATIONS = 100000;
|
|||
|
||||
var Client = require('../lib/client'),
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
util = require('util');
|
||||
util = require('util'),
|
||||
states = require('../lib/protocol').states;
|
||||
|
||||
var FakeSocket = function() {
|
||||
EventEmitter.call(this);
|
||||
|
@ -13,12 +14,12 @@ FakeSocket.prototype.write = function(){};
|
|||
var client = new Client();
|
||||
var socket = new FakeSocket();
|
||||
client.setSocket(socket);
|
||||
client.state = states.PLAY;
|
||||
|
||||
var testData = [
|
||||
{id: 0x0, params: {keepAliveId: 957759560}},
|
||||
{id: 0x3, 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: 0xe, params: {status: 1, x: 32, y: 64, z: 32, face: 3}}
|
||||
var testDataWrite = [
|
||||
{id: 0x00, params: {keepAliveId: 957759560}},
|
||||
{id: 0x01, params: {message: '<Bob> Hello World!'}},
|
||||
{id: 0x06, params: {x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true}},
|
||||
// TODO: add more packets for better quality data
|
||||
];
|
||||
|
||||
|
@ -26,20 +27,30 @@ var start, i, j;
|
|||
console.log('Beginning write test');
|
||||
start = Date.now();
|
||||
for(i = 0; i < ITERATIONS; i++) {
|
||||
for(j = 0; j < testData.length; j++) {
|
||||
client.write(testData[j].id, testData[j].params);
|
||||
for(j = 0; j < testDataWrite.length; j++) {
|
||||
client.write(testDataWrite[j].id, testDataWrite[j].params);
|
||||
}
|
||||
}
|
||||
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);
|
||||
socket.write = function(data) {
|
||||
inputData = Buffer.concat([inputData, data]);
|
||||
};
|
||||
for(i = 0; i < testData.length; i++) {
|
||||
client.write(testData[i].id, testData[i].params);
|
||||
for(i = 0; i < testDataRead.length; i++) {
|
||||
client.write(testDataRead[i].id, testDataRead[i].params);
|
||||
}
|
||||
|
||||
client.isServer = false;
|
||||
|
||||
console.log('Beginning read test');
|
||||
start = Date.now();
|
||||
for(i = 0; i < ITERATIONS; i++) {
|
||||
|
|
218
test/test.js
218
test/test.js
|
@ -1,5 +1,6 @@
|
|||
var mc = require('../')
|
||||
, protocol = mc.protocol
|
||||
, states = protocol.states
|
||||
, Client = mc.Client
|
||||
, Server = mc.Server
|
||||
, spawn = require('child_process').spawn
|
||||
|
@ -18,26 +19,31 @@ var mc = require('../')
|
|||
|
||||
var defaultServerProps = {
|
||||
'generator-settings': "",
|
||||
'op-permission-level': '4',
|
||||
'allow-nether': 'true',
|
||||
'level-name': 'world',
|
||||
'enable-query': 'false',
|
||||
'allow-flight': 'false',
|
||||
'announce-player-achievements': true,
|
||||
'server-port': '25565',
|
||||
'level-type': 'DEFAULT',
|
||||
'enable-rcon': 'false',
|
||||
'force-gamemode': 'false',
|
||||
'level-seed': "",
|
||||
'server-ip': "",
|
||||
'max-build-height': '256',
|
||||
'spawn-npcs': 'true',
|
||||
'white-list': 'false',
|
||||
'spawn-animals': 'true',
|
||||
'snooper-enabled': 'true',
|
||||
'hardcore': 'false',
|
||||
'texture-pack': '',
|
||||
'snooper-enabled': 'true',
|
||||
'online-mode': 'true',
|
||||
'resource-pack': '',
|
||||
'pvp': 'true',
|
||||
'difficulty': '1',
|
||||
'enable-command-block': 'false',
|
||||
'gamemode': '0',
|
||||
'player-idle-timeout': '0',
|
||||
'max-players': '20',
|
||||
'spawn-monsters': 'true',
|
||||
'generate-structures': 'true',
|
||||
|
@ -50,6 +56,7 @@ var values = {
|
|||
'int': 123456,
|
||||
'short': -123,
|
||||
'ushort': 123,
|
||||
'varint': 25992,
|
||||
'byte': -10,
|
||||
'ubyte': 8,
|
||||
'string': "hi hi this is my client string",
|
||||
|
@ -102,7 +109,9 @@ var values = {
|
|||
'intArray8': [1, 2, 3, 4],
|
||||
'intVector': {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() {
|
||||
|
@ -127,45 +136,51 @@ describe("packets", function() {
|
|||
client.end();
|
||||
});
|
||||
var packetId, packetInfo, field;
|
||||
for(packetId in protocol.packets) {
|
||||
if (!protocol.packets.hasOwnProperty(packetId)) continue;
|
||||
|
||||
packetId = parseInt(packetId, 10);
|
||||
packetInfo = protocol.packets[packetId];
|
||||
it("0x" + zfill(parseInt(packetId, 10).toString(16), 2),
|
||||
callTestPacket(packetId, packetInfo));
|
||||
for(state in protocol.packets) {
|
||||
if (!protocol.packets.hasOwnProperty(state)) continue;
|
||||
for(packetId in protocol.packets[state].toServer) {
|
||||
if (!protocol.packets[state].toServer.hasOwnProperty(packetId)) continue;
|
||||
packetId = parseInt(packetId, 10);
|
||||
packetInfo = protocol.get(packetId, state, true);
|
||||
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) {
|
||||
var batch = new Batch();
|
||||
batch.push(function(done) {
|
||||
testPacket(packetId, protocol.get(packetId, false), done);
|
||||
});
|
||||
batch.push(function(done) {
|
||||
testPacket(packetId, protocol.get(packetId, true), done);
|
||||
});
|
||||
batch.end(function(err, results) {
|
||||
done();
|
||||
});
|
||||
};
|
||||
client.state = state;
|
||||
serverClient.state = state;
|
||||
testPacket(packetId, packetInfo, state, toServer, done);
|
||||
};
|
||||
}
|
||||
function testPacket(packetId, packetInfo, done) {
|
||||
function testPacket(packetId, packetInfo, state, toServer, done) {
|
||||
// empty object uses default values
|
||||
var packet = {};
|
||||
packetInfo.forEach(function(field) {
|
||||
packet[field.name] = values[field.type];
|
||||
});
|
||||
serverClient.once(packetId, function(receivedPacket) {
|
||||
delete receivedPacket.id;
|
||||
assertPacketsMatch(packet, receivedPacket);
|
||||
client.once(packetId, function(clientReceivedPacket) {
|
||||
delete clientReceivedPacket.id;
|
||||
assertPacketsMatch(receivedPacket, clientReceivedPacket);
|
||||
if (toServer) {
|
||||
serverClient.once([state, packetId], function(receivedPacket) {
|
||||
delete receivedPacket.id;
|
||||
assertPacketsMatch(packet, receivedPacket);
|
||||
done();
|
||||
});
|
||||
serverClient.write(packetId, receivedPacket);
|
||||
});
|
||||
client.write(packetId, packet);
|
||||
client.write(packetId, packet);
|
||||
} else {
|
||||
client.once([state, packetId], function(receivedPacket) {
|
||||
delete receivedPacket.id;
|
||||
assertPacketsMatch(packet, receivedPacket);
|
||||
done();
|
||||
});
|
||||
serverClient.write(packetId, packet);
|
||||
}
|
||||
}
|
||||
function assertPacketsMatch(p1, p2) {
|
||||
packetInfo.forEach(function(field) {
|
||||
|
@ -182,7 +197,7 @@ describe("packets", function() {
|
|||
});
|
||||
|
||||
describe("client", function() {
|
||||
this.timeout(20000);
|
||||
this.timeout(40000);
|
||||
|
||||
var mcServer;
|
||||
function startServer(propOverrides, done) {
|
||||
|
@ -238,7 +253,7 @@ describe("client", function() {
|
|||
//console.error("[MC]", line);
|
||||
});
|
||||
function onLine(line) {
|
||||
if (/\[INFO\] Done/.test(line)) {
|
||||
if (/\[Server thread\/INFO\]: Done/.test(line)) {
|
||||
mcServer.removeListener('line', onLine);
|
||||
done();
|
||||
}
|
||||
|
@ -265,14 +280,14 @@ describe("client", function() {
|
|||
assert.ok(results.latency >= 0);
|
||||
assert.ok(results.latency <= 1000);
|
||||
delete results.latency;
|
||||
assert.deepEqual(results, {
|
||||
prefix: "§1",
|
||||
protocol: protocol.version,
|
||||
version: protocol.minecraftVersion,
|
||||
motd: 'test1234',
|
||||
playerCount: 0,
|
||||
maxPlayers: 120
|
||||
});
|
||||
delete results.favicon; // too lazy to figure it out
|
||||
/* assert.deepEqual(results, {
|
||||
version: {
|
||||
name: '1.7.4',
|
||||
protocol: 4
|
||||
},
|
||||
description: { text: "test1234" }
|
||||
});*/
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -284,34 +299,42 @@ describe("client", function() {
|
|||
password: process.env.MC_PASSWORD,
|
||||
});
|
||||
mcServer.on('line', function(line) {
|
||||
var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
|
||||
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
|
||||
if (! match) return;
|
||||
assert.strictEqual(match[1], client.session.username);
|
||||
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
||||
mcServer.stdin.write("say hello\n");
|
||||
});
|
||||
var chatCount = 0;
|
||||
client.on(0x01, function(packet) {
|
||||
client.on([states.PLAY, 0x01], function(packet) {
|
||||
assert.strictEqual(packet.levelType, 'default');
|
||||
assert.strictEqual(packet.difficulty, 1);
|
||||
assert.strictEqual(packet.dimension, 0);
|
||||
assert.strictEqual(packet.gameMode, 0);
|
||||
client.write(0x03, {
|
||||
client.write(0x01, {
|
||||
message: "hello everyone; I have logged in."
|
||||
});
|
||||
});
|
||||
client.on(0x03, function(packet) {
|
||||
client.on([states.PLAY, 0x02], function(packet) {
|
||||
chatCount += 1;
|
||||
assert.ok(chatCount <= 2);
|
||||
var message = JSON.parse(packet.message);
|
||||
if (chatCount === 1) {
|
||||
assert.strictEqual(message.translate, "chat.type.text");
|
||||
assert.strictEqual(message.using[0], client.session.username);
|
||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
||||
assert.deepEqual(message["with"][0], {
|
||||
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) {
|
||||
assert.strictEqual(message.translate, "chat.type.announcement");
|
||||
assert.strictEqual(message.using[0], "Server");
|
||||
assert.strictEqual(message.using[1], "hello");
|
||||
assert.strictEqual(message["with"][0], "Server");
|
||||
assert.deepEqual(message["with"][1], { text: "",
|
||||
extra: ["hello"]
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
@ -323,34 +346,42 @@ describe("client", function() {
|
|||
username: 'Player',
|
||||
});
|
||||
mcServer.on('line', function(line) {
|
||||
var match = line.match(/\[INFO\] <(.+?)> (.+)$/);
|
||||
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
|
||||
if (! match) return;
|
||||
assert.strictEqual(match[1], 'Player');
|
||||
assert.strictEqual(match[2], "hello everyone; I have logged in.");
|
||||
mcServer.stdin.write("say hello\n");
|
||||
});
|
||||
var chatCount = 0;
|
||||
client.on(0x01, function(packet) {
|
||||
client.on([states.PLAY, 0x01], function(packet) {
|
||||
assert.strictEqual(packet.levelType, 'default');
|
||||
assert.strictEqual(packet.difficulty, 1);
|
||||
assert.strictEqual(packet.dimension, 0);
|
||||
assert.strictEqual(packet.gameMode, 0);
|
||||
client.write(0x03, {
|
||||
client.write(0x01, {
|
||||
message: "hello everyone; I have logged in."
|
||||
});
|
||||
});
|
||||
client.on(0x03, function(packet) {
|
||||
client.on([states.PLAY, 0x02], function(packet) {
|
||||
chatCount += 1;
|
||||
assert.ok(chatCount <= 2);
|
||||
var message = JSON.parse(packet.message);
|
||||
if (chatCount === 1) {
|
||||
assert.strictEqual(message.translate, "chat.type.text");
|
||||
assert.strictEqual(message.using[0], "Player");
|
||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
||||
assert.deepEqual(message["with"][0], {
|
||||
clickEvent: {
|
||||
action: "suggest_command",
|
||||
value: "/msg Player "
|
||||
},
|
||||
text: "Player"
|
||||
});
|
||||
assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
|
||||
} else if (chatCount === 2) {
|
||||
assert.strictEqual(message.translate, "chat.type.announcement");
|
||||
assert.strictEqual(message.using[0], "Server");
|
||||
assert.strictEqual(message.using[1], "hello");
|
||||
assert.strictEqual(message["with"][0], "Server");
|
||||
assert.deepEqual(message["with"][1], { text: "",
|
||||
extra: ["hello"]
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
@ -362,8 +393,8 @@ describe("client", function() {
|
|||
username: 'Player',
|
||||
});
|
||||
var gotKicked = false;
|
||||
client.on(0xff, function(packet) {
|
||||
assert.strictEqual(packet.reason, "Failed to verify username!");
|
||||
client.on([states.LOGIN, 0x00], function(packet) {
|
||||
assert.strictEqual(packet.reason, '"Failed to verify username!"');
|
||||
gotKicked = true;
|
||||
});
|
||||
client.on('end', function() {
|
||||
|
@ -377,23 +408,26 @@ describe("client", function() {
|
|||
var client = mc.createClient({
|
||||
username: 'Player',
|
||||
});
|
||||
client.on(0x01, function(packet) {
|
||||
client.write(0x03, {
|
||||
client.on([states.PLAY, 0x01], function(packet) {
|
||||
client.write(0x01, {
|
||||
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);
|
||||
assert.strictEqual(message.translate, "chat.type.text");
|
||||
assert.strictEqual(message.using[0], "Player");
|
||||
assert.strictEqual(message.using[1], "hello everyone; I have logged in.");
|
||||
assert.deepEqual(message["with"][0], {
|
||||
clickEvent: {
|
||||
action: "suggest_command",
|
||||
value: "/msg Player "
|
||||
},
|
||||
text: "Player"
|
||||
});
|
||||
assert.strictEqual(message["with"][1], "hello everyone; I have logged in.");
|
||||
setTimeout(function() {
|
||||
done();
|
||||
}, 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);
|
||||
delete results.latency;
|
||||
assert.deepEqual(results, {
|
||||
prefix: "§1",
|
||||
protocol: protocol.version,
|
||||
version: protocol.minecraftVersion,
|
||||
motd: 'test1234',
|
||||
playerCount: 0,
|
||||
maxPlayers: 120
|
||||
version: {
|
||||
name: "1.7.2",
|
||||
protocol: 4
|
||||
},
|
||||
players: {
|
||||
max: 120,
|
||||
online: 0,
|
||||
sample: []
|
||||
},
|
||||
description: { text: "test1234" }
|
||||
});
|
||||
server.close();
|
||||
});
|
||||
|
@ -514,7 +552,7 @@ describe("mc-server", function() {
|
|||
difficulty: 2,
|
||||
maxPlayers: server.maxPlayers
|
||||
});
|
||||
client.on(0x03, function(packet) {
|
||||
client.on([states.PLAY, 0x01], function(packet) {
|
||||
var message = '<' + client.username + '>' + ' ' + packet.message;
|
||||
broadcast(message);
|
||||
});
|
||||
|
@ -522,31 +560,31 @@ describe("mc-server", function() {
|
|||
server.on('close', done);
|
||||
server.on('listening', function() {
|
||||
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.levelType, 'default');
|
||||
assert.strictEqual(packet.dimension, 0);
|
||||
assert.strictEqual(packet.difficulty, 2);
|
||||
player1.once(0x03, function(packet) {
|
||||
assert.strictEqual(packet.message, 'player2 joined the game.');
|
||||
player1.once(0x03, function(packet) {
|
||||
assert.strictEqual(packet.message, '<player2> hi');
|
||||
player2.once(0x03, fn);
|
||||
player1.once(0x02, function(packet) {
|
||||
assert.strictEqual(packet.message, '{"text":"player2 joined the game."}');
|
||||
player1.once(0x02, function(packet) {
|
||||
assert.strictEqual(packet.message, '{"text":"<player2> hi"}');
|
||||
player2.once(0x02, fn);
|
||||
function fn(packet) {
|
||||
if (/^<player2>/.test(packet.message)) {
|
||||
player2.once(0x03, fn);
|
||||
if (/<player2>/.test(packet.message)) {
|
||||
player2.once(0x02, fn);
|
||||
return;
|
||||
}
|
||||
assert.strictEqual(packet.message, '<player1> hello');
|
||||
player1.once(0x03, function(packet) {
|
||||
assert.strictEqual(packet.message, 'player2 left the game.');
|
||||
assert.strictEqual(packet.message, '{"text":"<player1> hello"}');
|
||||
player1.once(0x02, function(packet) {
|
||||
assert.strictEqual(packet.message, '{"text":"player2 left the game."}');
|
||||
player1.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' });
|
||||
});
|
||||
|
@ -558,7 +596,7 @@ describe("mc-server", function() {
|
|||
if (!server.clients.hasOwnProperty(clientId)) continue;
|
||||
|
||||
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() {
|
||||
var client = mc.createClient({ username: 'lalalal', });
|
||||
client.on(0x01, function() {
|
||||
client.on([states.PLAY, 0x01], function() {
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue