enable cross version with an option in createClient and createServer :

* put parsePacketData in deserializer and createPacketBuffer in serializer
* remove packets from the index and expose readPacket instead
* load packets when needed in various files
* make tests test every supported version
static cross version of #234, fix #65, fix #240
This commit is contained in:
Romain Beaumont 2015-09-29 22:41:41 +02:00
parent df2caf74cb
commit 917b6adda1
15 changed files with 726 additions and 669 deletions

View File

@ -7,7 +7,7 @@ Parse and serialize minecraft packets, plus authentication and encryption.
## Features ## Features
* Supports Minecraft version 1.8.8 * Supports Minecraft version 1.8.8 and 1.9
* Parses all packets and emits events with packet fields as JavaScript * Parses all packets and emits events with packet fields as JavaScript
objects. objects.
* Send a packet by supplying fields as a JavaScript object. * Send a packet by supplying fields as a JavaScript object.
@ -121,8 +121,7 @@ See [doc](doc/README.md)
## Testing ## Testing
* Ensure your system has the `java` executable in `PATH`. * Ensure your system has the `java` executable in `PATH`.
* Download the appropriate version of `minecraft_server.jar`. * `MC_SERVER_JAR_DIR=some/path/to/store/minecraft/server/ MC_USERNAME=email@example.com MC_PASSWORD=password npm test`
* `MC_SERVER_JAR=path/to/minecraft_server.jar MC_USERNAME=email@example.com MC_PASSWORD=password npm test`
## Debugging ## Debugging

View File

@ -1,12 +1,10 @@
machine: machine:
environment: environment:
MC_SERVER_JAR: /home/ubuntu/node-minecraft-protocol/minecraft-server/minecraft_server.jar MC_SERVER_JAR_DIR: /home/ubuntu/node-minecraft-protocol/minecraft-server/
node: node:
version: 0.10.28 version: 0.10.28
java: java:
version: openjdk7 version: openjdk7
test: dependencies:
override: pre:
- mkdir -p /home/ubuntu/node-minecraft-protocol/minecraft-server/ - mkdir minecraft-server
- node_modules/.bin/downloadMinecraft `node -e 'console.log(require("./src/version").minecraftVersion)'` $MC_SERVER_JAR
- npm test

View File

@ -84,6 +84,7 @@ Returns a `Client` instance and perform login.
* clientToken : generated if a password is given * clientToken : generated if a password is given
* accessToken : generated if a password is given * accessToken : generated if a password is given
* keepAlive : send keep alive packets : default to true * keepAlive : send keep alive packets : default to true
* version : 1.8 or 1.9
## Client ## Client

View File

@ -34,7 +34,7 @@
"gulp-plumber": "^1.0.1", "gulp-plumber": "^1.0.1",
"gulp-sourcemaps": "^1.3.0", "gulp-sourcemaps": "^1.3.0",
"intelli-espower-loader": "^1.0.0", "intelli-espower-loader": "^1.0.0",
"mocha": "~1.8.2", "mocha": "~2.3.3",
"power-assert": "^1.0.0", "power-assert": "^1.0.0",
"source-map-support": "^0.3.2", "source-map-support": "^0.3.2",
"minecraft-wrap": "~0.5.4" "minecraft-wrap": "~0.5.4"

View File

@ -1,19 +1,12 @@
var version = require('./version');
var packets = require('minecraft-data')(version.majorVersion).protocol.states;
var readPackets = require("./packets").readPackets; var readPackets = require("./packets").readPackets;
var packetIndexes = readPackets(packets, states);
var utils = require("./utils"); var utils = require("./utils");
var serializer = require("./transforms/serializer"); var serializer = require("./transforms/serializer");
module.exports = { module.exports = {
Client: require('./client'), Client: require('./client'),
protocol: require('./protocol'), protocol: require('./protocol'),
createPacketBuffer: serializer.createPacketBuffer,
parsePacketData: serializer.parsePacketData,
packetFields: packetIndexes.packetFields,
packetNames: packetIndexes.packetNames,
packetIds: packetIndexes.packetIds,
packetStates: packetIndexes.packetStates,
types: serializer.types, types: serializer.types,
get: serializer.get, get: serializer.get,
readPackets:readPackets,
supportedVersions:require("./version").supportedVersions
}; };

View File

@ -5,10 +5,6 @@ var compression = require('./transforms/compression');
var framing = require('./transforms/framing'); var framing = require('./transforms/framing');
var crypto = require('crypto'); var crypto = require('crypto');
var states = serializer.states; var states = serializer.states;
var version = require('./version');
var packets = require('minecraft-data')(version.majorVersion).protocol.states;
var readPackets = require("./packets").readPackets;
var packetIndexes = readPackets(packets, states);
class Client extends EventEmitter class Client extends EventEmitter
@ -24,11 +20,16 @@ class Client extends EventEmitter
deserializer; deserializer;
isServer; isServer;
constructor(isServer) { constructor(isServer,version) {
super(); super();
this.serializer = serializer.createSerializer({ isServer }); var mcData=require("minecraft-data")(version);
this.deserializer = serializer.createDeserializer({ isServer, packetsToParse: this.packetsToParse }); var packets = mcData.protocol.states;
var readPackets = require("./packets").readPackets;
var packetIndexes = readPackets(packets, states);
this.serializer = serializer.createSerializer({ isServer, version:version});
this.deserializer = serializer.createDeserializer({ isServer, packetsToParse: this.packetsToParse, version:version});
this.isServer = !!isServer; this.isServer = !!isServer;
this.on('newListener', function(event, listener) { this.on('newListener', function(event, listener) {

View File

@ -1,6 +1,5 @@
var mcHexDigest=require("./mcHexDigest"); var mcHexDigest=require("./mcHexDigest");
var ursa=require("./ursa"); var ursa=require("./ursa");
var version = require("./version");
var net = require('net'); var net = require('net');
var dns = require('dns'); var dns = require('dns');
var Client = require('./client'); var Client = require('./client');
@ -42,8 +41,12 @@ function createClient(options) {
var haveCredentials = options.password != null || (clientToken != null && accessToken != 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 optVersion = options.version || require("./version").defaultVersion;
var mcData=require("minecraft-data")(optVersion);
var version = mcData.version;
var client = new Client(false);
var client = new Client(false,version.majorVersion);
client.on('connect', onConnect); client.on('connect', onConnect);
if(keepAlive) client.on('keep_alive', onKeepAlive); if(keepAlive) client.on('keep_alive', onKeepAlive);
client.once('encryption_begin', onEncryptionKeyRequest); client.once('encryption_begin', onEncryptionKeyRequest);

View File

@ -1,6 +1,5 @@
var mcHexDigest=require("./mcHexDigest"); var mcHexDigest=require("./mcHexDigest");
var ursa=require("./ursa"); var ursa=require("./ursa");
var version = require("./version");
var crypto = require('crypto'); var crypto = require('crypto');
var Yggdrasil = require('./yggdrasil.js'); var Yggdrasil = require('./yggdrasil.js');
var validateSession = Yggdrasil.validateSession; var validateSession = Yggdrasil.validateSession;
@ -28,9 +27,13 @@ function createServer(options) {
var enableKeepAlive = options.keepAlive == null ? true : options.keepAlive; var enableKeepAlive = options.keepAlive == null ? true : options.keepAlive;
var optVersion = options.version || require("./version").defaultVersion;
var mcData=require("minecraft-data")(optVersion);
var version = mcData.version;
var serverKey = ursa.generatePrivateKey(1024); var serverKey = ursa.generatePrivateKey(1024);
var server = new Server(options); var server = new Server(version.majorVersion);
server.motd = options.motd || "A Minecraft server"; server.motd = options.motd || "A Minecraft server";
server.maxPlayers = options['max-players'] || 20; server.maxPlayers = options['max-players'] || 20;
server.playerCount = 0; server.playerCount = 0;

View File

@ -3,10 +3,7 @@ var Server = require('./server');
var Yggdrasil = require('./yggdrasil.js'); var Yggdrasil = require('./yggdrasil.js');
var serializer = require("./transforms/serializer"); var serializer = require("./transforms/serializer");
var utils = require("./utils"); var utils = require("./utils");
var version = require("./version");
var packets = require('minecraft-data')(version.majorVersion).protocol.states;
var readPackets = require("./packets").readPackets; var readPackets = require("./packets").readPackets;
var packetIndexes = readPackets(packets, serializer.states);
var createClient = require("./createClient"); var createClient = require("./createClient");
var createServer = require("./createServer"); var createServer = require("./createServer");
@ -16,16 +13,10 @@ module.exports = {
Client: Client, Client: Client,
Server: Server, Server: Server,
states: serializer.states, states: serializer.states,
createPacketBuffer: serializer.createPacketBuffer, createSerializer:serializer.createSerializer,
parsePacketData: serializer.parsePacketData, createDeserializer:serializer.createDeserializer,
packetFields: packetIndexes.packetFields, readPackets:readPackets,
packetNames: packetIndexes.packetNames,
packetIds: packetIndexes.packetIds,
packetStates: packetIndexes.packetStates,
types: serializer.types,
get: serializer.get,
ping: require('./ping'), ping: require('./ping'),
yggdrasil: Yggdrasil, yggdrasil: Yggdrasil,
version: version.version, supportedVersions:require("./version").supportedVersions
minecraftVersion: version.minecraftVersion
}; };

View File

@ -1,15 +1,17 @@
var net = require('net') var net = require('net');
, Client = require('./client') var Client = require('./client');
, states = require('./transforms/serializer').states; var states = require('./transforms/serializer').states;
var version = require('./version');
module.exports = ping; module.exports = ping;
function ping(options, cb) { function ping(options, cb) {
var host = options.host || 'localhost'; var host = options.host || 'localhost';
var port = options.port || 25565; var port = options.port || 25565;
var optVersion = options.version || require("./version").defaultVersion;
var mcData=require("minecraft-data")(optVersion);
var version = mcData.version;
var client = new Client(); var client = new Client(false,version.majorVersion);
client.on('error', function(err) { client.on('error', function(err) {
cb(err); cb(err);
}); });

View File

@ -10,16 +10,17 @@ class Server extends EventEmitter
decipher=null; decipher=null;
clients={}; clients={};
constructor() { constructor(version) {
super(); super();
this.version=version;
} }
listen(port, host) { listen(port, host) {
var self = this; var self = this;
var nextId = 0; var nextId = 0;
self.socketServer = net.createServer(); self.socketServer = net.createServer();
self.socketServer.on('connection', function(socket) { self.socketServer.on('connection', socket => {
var client = new Client(true); var client = new Client(true,this.version);
client._end = client.end; client._end = client.end;
client.end = function end(endReason) { client.end = function end(endReason) {
endReason='{"text":"'+endReason+'"}'; endReason='{"text":"'+endReason+'"}';

View File

@ -13,9 +13,6 @@ module.exports.createDeserializer = function(obj) {
return new Deserializer(obj); return new Deserializer(obj);
}; };
module.exports.createPacketBuffer=createPacketBuffer;
module.exports.parsePacketData=parsePacketData;
// This is really just for the client. // This is really just for the client.
var states = { var states = {
"HANDSHAKING": "handshaking", "HANDSHAKING": "handshaking",
@ -25,54 +22,61 @@ var states = {
}; };
module.exports.states = states; module.exports.states = states;
module.exports.get = get;
var NMProtocols = require("../protocol"); var NMProtocols = require("../protocol");
var numeric = require("../datatypes/numeric"); var numeric = require("../datatypes/numeric");
var utils = require("../datatypes/utils"); var utils = require("../datatypes/utils");
var minecraft = require("../datatypes/minecraft"); var minecraft = require("../datatypes/minecraft");
var structures = require("../datatypes/structures"); var structures = require("../datatypes/structures");
var conditional = require("../datatypes/conditional"); var conditional = require("../datatypes/conditional");
var readPackets = require("../packets").readPackets;
function createProtocol(types)
{
var proto = new NMProtocols(); var proto = new NMProtocols();
proto.addTypes(numeric); proto.addTypes(numeric);
proto.addTypes(utils); proto.addTypes(utils);
proto.addTypes(minecraft); proto.addTypes(minecraft);
proto.addTypes(structures); proto.addTypes(structures);
proto.addTypes(conditional); proto.addTypes(conditional);
proto.addTypes(types);
return proto;
}
module.exports.types = proto.types;
var version = require('../version'); class Serializer extends Transform {
var packets = require('minecraft-data')(version.majorVersion).protocol; constructor({ state = states.HANDSHAKING, isServer = false , version} = {}) {
proto.addTypes(packets.types); super({ writableObjectMode: true });
this.protocolState = state;
this.isServer = isServer;
this.version = version;
var readPackets = require("../packets").readPackets; var mcData=require("minecraft-data")(version);
var packetIndexes = readPackets(packets.states, states); var packets = mcData.protocol.states;
var packetIndexes = readPackets(packets, states);
var packetFields = packetIndexes.packetFields; this.proto=createProtocol(mcData.protocol.types);
var packetNames = packetIndexes.packetNames;
var packetIds = packetIndexes.packetIds;
var packetStates = packetIndexes.packetStates;
this.packetFields = packetIndexes.packetFields;
this.packetIds = packetIndexes.packetIds;
}
// TODO : This does NOT contain the length prefix anymore. // TODO : This does NOT contain the length prefix anymore.
function createPacketBuffer(packetName, state, params, isServer) { createPacketBuffer(packetName, params) {
var direction = !isServer ? 'toServer' : 'toClient'; var direction = !this.isServer ? 'toServer' : 'toClient';
var packetId = packetIds[state][direction][packetName]; var packetId = this.packetIds[this.protocolState][direction][packetName];
assert.notEqual(packetId, undefined, `${state}.${isServer}.${packetName} : ${packetId}`); assert.notEqual(packetId, undefined, `${this.protocolState}.${this.isServer}.${packetName} : ${packetId}`);
var packet = get(packetName, state, !isServer); var packet = this.packetFields[this.protocolState][direction][packetName];
packet=packet ? packet : null;
assert.notEqual(packet, null); assert.notEqual(packet, null);
var length = utils.varint[2](packetId); var length = utils.varint[2](packetId);
tryCatch(() => { tryCatch(() => {
length += structures.container[2].call(proto, params, packet, {}); length += structures.container[2].call(this.proto, params, packet, {});
//length += proto.sizeOf(params, ["container", packet], {}); //length += proto.sizeOf(params, ["container", packet], {});
}, (e) => { }, (e) => {
e.field = [state, direction, packetName, e.field].join("."); e.field = [this.protocolState, direction, packetName, e.field].join(".");
e.message = `SizeOf error for ${e.field} : ${e.message}`; e.message = `SizeOf error for ${e.field} : ${e.message}`;
throw e; throw e;
}); });
@ -80,36 +84,56 @@ function createPacketBuffer(packetName, state, params, isServer) {
var buffer = new Buffer(length); var buffer = new Buffer(length);
var offset = utils.varint[1](packetId, buffer, 0); var offset = utils.varint[1](packetId, buffer, 0);
tryCatch(() => { tryCatch(() => {
offset = structures.container[1].call(proto, params, buffer, offset, packet, {}); offset = structures.container[1].call(this.proto, params, buffer, offset, packet, {});
//offset = proto.write(params, buffer, offset, ["container", packet], {}); //offset = proto.write(params, buffer, offset, ["container", packet], {});
}, (e) => { }, (e) => {
e.field = [state, direction, packetName, e.field].join("."); e.field = [this.protocolState, direction, packetName, e.field].join(".");
e.message = `Write error for ${e.field} : ${e.message}`; e.message = `Write error for ${e.field} : ${e.message}`;
throw e; throw e;
}); });
return buffer; return buffer;
} }
_transform(chunk, enc, cb) {
function get(packetName, state, toServer) { try {
var direction = toServer ? "toServer" : "toClient"; var buf = this.createPacketBuffer(chunk.packetName, chunk.params);
var packetInfo = packetFields[state][direction][packetName]; this.push(buf);
if(!packetInfo) { return cb();
return null; } catch (e) {
return cb(e);
}
} }
return packetInfo;
} }
function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": true}) { class Deserializer extends Transform {
constructor({ state = states.HANDSHAKING, isServer = false, packetsToParse = {"packet": true}, version } = {}) {
super({ readableObjectMode: true });
this.protocolState = state;
this.isServer = isServer;
this.packetsToParse = packetsToParse;
this.version = version;
var mcData=require("minecraft-data")(version);
var packets = mcData.protocol.states;
var packetIndexes = readPackets(packets, states);
this.proto=createProtocol(mcData.protocol.types);
this.packetFields = packetIndexes.packetFields;
this.packetNames = packetIndexes.packetNames;
}
parsePacketData(buffer) {
var { value: packetId, size: cursor } = utils.varint[0](buffer, 0); var { value: packetId, size: cursor } = utils.varint[0](buffer, 0);
var direction = isServer ? "toServer" : "toClient"; var direction = this.isServer ? "toServer" : "toClient";
var packetName = packetNames[state][direction][packetId]; var packetName = this.packetNames[this.protocolState][direction][packetId];
var results = { var results = {
metadata: { metadata: {
name: packetName, name: packetName,
id: packetId, id: packetId,
state state:this.protocolState
}, },
data: {}, data: {},
buffer buffer
@ -118,22 +142,23 @@ function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": tr
// Only parse the packet if there is a need for it, AKA if there is a listener // Only parse the packet if there is a need for it, AKA if there is a listener
// attached to it. // attached to it.
var shouldParse = var shouldParse =
(packetsToParse.hasOwnProperty(packetName) && packetsToParse[packetName] > 0) || (this.packetsToParse.hasOwnProperty(packetName) && this.packetsToParse[packetName] > 0) ||
(packetsToParse.hasOwnProperty("packet") && packetsToParse["packet"] > 0); (this.packetsToParse.hasOwnProperty("packet") && this.packetsToParse["packet"] > 0);
if (!shouldParse) if (!shouldParse)
return results; return results;
var packetInfo = get(packetName, state, isServer); var packetInfo = this.packetFields[this.protocolState][direction][packetName];
packetInfo=packetInfo ? packetInfo : null;
if(packetInfo === null) if(packetInfo === null)
throw new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")") throw new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")")
else else
debug("read packetId " + state + "." + packetName + " (0x" + packetId.toString(16) + ")"); debug("read packetId " + this.protocolState + "." + packetName + " (0x" + packetId.toString(16) + ")");
var res; var res;
tryCatch(() => { tryCatch(() => {
res = proto.read(buffer, cursor, ["container", packetInfo], {}); res = this.proto.read(buffer, cursor, ["container", packetInfo], {});
}, (e) => { }, (e) => {
e.field = [state, direction, packetName, e.field].join("."); e.field = [this.protocolState, direction, packetName, e.field].join(".");
e.message = `Read error for ${e.field} : ${e.message}`; e.message = `Read error for ${e.field} : ${e.message}`;
throw e; throw e;
}); });
@ -146,36 +171,11 @@ function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": tr
return results; return results;
} }
class Serializer extends Transform {
constructor({ state = states.HANDSHAKING, isServer = false } = {}) {
super({ writableObjectMode: true });
this.protocolState = state;
this.isServer = isServer;
}
_transform(chunk, enc, cb) {
try {
var buf = createPacketBuffer(chunk.packetName, this.protocolState, chunk.params, this.isServer);
this.push(buf);
return cb();
} catch (e) {
return cb(e);
}
}
}
class Deserializer extends Transform {
constructor({ state = states.HANDSHAKING, isServer = false, packetsToParse = {"packet": true} } = {}) {
super({ readableObjectMode: true });
this.protocolState = state;
this.isServer = isServer;
this.packetsToParse = packetsToParse;
}
_transform(chunk, enc, cb) { _transform(chunk, enc, cb) {
var packet; var packet;
try { try {
packet = parsePacketData(chunk, this.protocolState, this.isServer, this.packetsToParse); packet = this.parsePacketData(chunk);
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }

View File

@ -1,3 +1,4 @@
var majorVersion='1.8'; module.exports={
var mcData=require("minecraft-data")(majorVersion); defaultVersion:'1.8',
module.exports=mcData.version; supportedVersions:['1.8','1.9']
};

View File

@ -11,21 +11,24 @@ var testDataWrite = [
// TODO: add more packets for better quality data // TODO: add more packets for better quality data
]; ];
mc.supportedVersions.forEach(function(supportedVersion){
var inputData = []; var inputData = [];
var serializer=new mc.createSerializer({state:states.PLAY,isServer:false,version:supportedVersion});
var start, i, j; var start, i, j;
console.log('Beginning write test'); console.log('Beginning write test for '+supportedVersion);
start = Date.now(); start = Date.now();
for(i = 0; i < ITERATIONS; i++) { for(i = 0; i < ITERATIONS; i++) {
for(j = 0; j < testDataWrite.length; j++) { for(j = 0; j < testDataWrite.length; j++) {
inputData.push(mc.createPacketBuffer(testDataWrite[j].name, states.PLAY, testDataWrite[j].params, false)); inputData.push(serializer.createPacketBuffer(testDataWrite[j].name, 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');
console.log('Beginning read test'); var deserializer=new mc.createDeserializer({state:states.PLAY,isServer:true,version:supportedVersion});
console.log('Beginning read test for '+supportedVersion);
start = Date.now(); start = Date.now();
for (j = 0; j < inputData.length; j++) { for (j = 0; j < inputData.length; j++) {
mc.parsePacketData(inputData[j], states.PLAY, true); deserializer.parsePacketData(inputData[j]);
} }
console.log('Finished read test in ' + (Date.now() - start) / 1000 + ' seconds'); console.log('Finished read test in ' + (Date.now() - start) / 1000 + ' seconds');
});

View File

@ -1,20 +1,17 @@
var mc = require('../') var mc = require('../');
, states = mc.states var states = mc.states;
, Client = mc.Client var Client = mc.Client;
, Server = mc.Server var Server = mc.Server;
, path = require('path') var path = require('path');
, fs = require('fs') var fs = require('fs');
, net = require('net') var net = require('net');
, assert = require('power-assert') var assert = require('power-assert');
, MC_SERVER_JAR = process.env.MC_SERVER_JAR var SURVIVE_TIME = 10000;
, SURVIVE_TIME = 10000 var getFieldInfo = require('../dist/utils').getFieldInfo;
, MC_SERVER_PATH = path.join(__dirname, 'server') var getField = require('../dist/utils').getField;
, getFieldInfo = require('../dist/utils').getFieldInfo var MC_SERVER_PATH = path.join(__dirname, 'server');
, getField = require('../dist/utils').getField
;
var Wrap = require('minecraft-wrap').Wrap; var Wrap = require('minecraft-wrap').Wrap;
var wrap=new Wrap(MC_SERVER_JAR,MC_SERVER_PATH);
function evalCount(count, fields) { function evalCount(count, fields) {
if(fields[count["field"]] in count["map"]) if(fields[count["field"]] in count["map"])
@ -48,7 +45,7 @@ var values = {
}, },
'container': function(typeArgs, context) { 'container': function(typeArgs, context) {
var results = { var results = {
"..": context; "..": context
}; };
for(var index in typeArgs) { for(var index in typeArgs) {
results[typeArgs[index].name] = getValue(typeArgs[index].type, results); results[typeArgs[index].name] = getValue(typeArgs[index].type, results);
@ -111,16 +108,30 @@ function getValue(_type, packet) {
throw new Error("No value for type " + fieldInfo.type); throw new Error("No value for type " + fieldInfo.type);
} }
describe("packets", function() { var download = require('minecraft-wrap').download;
mc.supportedVersions.forEach(function(supportedVersion){
var mcData=require("minecraft-data")(supportedVersion);
var version=mcData.version;
var MC_SERVER_JAR_DIR = process.env.MC_SERVER_JAR_DIR;
var MC_SERVER_JAR = MC_SERVER_JAR_DIR+"/minecraft_server."+version.minecraftVersion+".jar";
var wrap=new Wrap(MC_SERVER_JAR,MC_SERVER_PATH);
var packets = mcData.protocol.states;
var packetIndexes = mc.readPackets(packets, states);
var packetFields = packetIndexes.packetFields;
describe("packets "+version.minecraftVersion, function() {
var client, server, serverClient; var client, server, serverClient;
before(function(done) { before(function(done) {
server = new Server(); server = new Server(version.majorVersion);
server.once('listening', function() { server.once('listening', function() {
server.once('connection', function(c) { server.once('connection', function(c) {
serverClient = c; serverClient = c;
done(); done();
}); });
client = new Client(); client = new Client(false,version.majorVersion);
client.setSocket(net.connect(25565, 'localhost')); client.setSocket(net.connect(25565, 'localhost'));
}); });
server.listen(25565, 'localhost'); server.listen(25565, 'localhost');
@ -133,17 +144,19 @@ describe("packets", function() {
client.end(); client.end();
}); });
var packetName, packetInfo, field; var packetName, packetInfo, field;
for(state in mc.packetFields) { for(state in packetFields) {
if(!mc.packetFields.hasOwnProperty(state)) continue; if(!packetFields.hasOwnProperty(state)) continue;
for(packetName in mc.packetFields[state].toServer) { for(packetName in packetFields[state].toServer) {
if(!mc.packetFields[state].toServer.hasOwnProperty(packetName)) continue; if(!packetFields[state].toServer.hasOwnProperty(packetName)) continue;
packetInfo = mc.get(packetName, state, true); packetInfo = packetFields[state]["toServer"][packetName];
packetInfo=packetInfo ? packetInfo : null;
it(state + ",ServerBound," + packetName, it(state + ",ServerBound," + packetName,
callTestPacket(packetName, packetInfo, state, true)); callTestPacket(packetName, packetInfo, state, true));
} }
for(packetName in mc.packetFields[state].toClient) { for(packetName in packetFields[state].toClient) {
if(!mc.packetFields[state].toClient.hasOwnProperty(packetName)) continue; if(!packetFields[state].toClient.hasOwnProperty(packetName)) continue;
packetInfo = mc.get(packetName, state, false); packetInfo = packetFields[state]["toClient"][packetName];
packetInfo=packetInfo ? packetInfo : null;
it(state + ",ClientBound," + packetName, it(state + ",ClientBound," + packetName,
callTestPacket(packetName, packetInfo, state, false)); callTestPacket(packetName, packetInfo, state, false));
} }
@ -197,9 +210,19 @@ describe("packets", function() {
} }
}); });
describe("client", function() { describe("client "+version.minecraftVersion, function() {
this.timeout(10 * 60 * 1000); this.timeout(10 * 60 * 1000);
before(function(done){
console.log(MC_SERVER_JAR);
fs.exists(MC_SERVER_JAR,function(exists){
if(exists)
done();
else
download(version.minecraftVersion,MC_SERVER_JAR,done);
});
});
afterEach(function(done) { afterEach(function(done) {
wrap.stopServer(function(err){ wrap.stopServer(function(err){
if(err) if(err)
@ -224,7 +247,9 @@ describe("client", function() {
}, function(err) { }, function(err) {
if(err) if(err)
return done(err); return done(err);
mc.ping({}, function(err, results) { mc.ping({
version: version.majorVersion
}, function(err, results) {
if(err) return done(err); if(err) return done(err);
assert.ok(results.latency >= 0); assert.ok(results.latency >= 0);
assert.ok(results.latency <= 1000); assert.ok(results.latency <= 1000);
@ -248,6 +273,7 @@ describe("client", function() {
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,
version: version.majorVersion
}); });
wrap.on('line', function(line) { wrap.on('line', function(line) {
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/); var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
@ -278,6 +304,7 @@ describe("client", function() {
return done(err); return done(err);
var client = mc.createClient({ var client = mc.createClient({
username: 'Player', username: 'Player',
version: version.majorVersion
}); });
wrap.on('line', function(line) { wrap.on('line', function(line) {
var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/); var match = line.match(/\[Server thread\/INFO\]: <(.+?)> (.+)/);
@ -329,6 +356,7 @@ describe("client", function() {
return done(err); return done(err);
var client = mc.createClient({ var client = mc.createClient({
username: 'Player', username: 'Player',
version: version.majorVersion
}); });
var gotKicked = false; var gotKicked = false;
client.on('disconnect', function(packet) { client.on('disconnect', function(packet) {
@ -347,6 +375,7 @@ describe("client", function() {
return done(err); return done(err);
var client = mc.createClient({ var client = mc.createClient({
username: 'Player', username: 'Player',
version: version.majorVersion
}); });
client.on("login", function(packet) { client.on("login", function(packet) {
client.write("chat", { client.write("chat", {
@ -371,9 +400,12 @@ describe("client", function() {
}); });
}); });
}); });
describe("mc-server", function() { describe("mc-server "+version.minecraftVersion, function() {
it("starts listening and shuts down cleanly", function(done) { it("starts listening and shuts down cleanly", function(done) {
var server = mc.createServer({'online-mode': false}); var server = mc.createServer({
'online-mode': false,
version: version.majorVersion
});
var listening = false; var listening = false;
server.on('listening', function() { server.on('listening', function() {
listening = true; listening = true;
@ -389,6 +421,7 @@ describe("mc-server", function() {
'online-mode': false, 'online-mode': false,
kickTimeout: 100, kickTimeout: 100,
checkTimeoutInterval: 10, checkTimeoutInterval: 10,
version: version.majorVersion
}); });
var count = 2; var count = 2;
server.on('connection', function(client) { server.on('connection', function(client) {
@ -401,7 +434,7 @@ describe("mc-server", function() {
resolve(); resolve();
}); });
server.on('listening', function() { server.on('listening', function() {
var client = new mc.Client(); var client = new mc.Client(false,version.majorVersion);
client.on('end', function() { client.on('end', function() {
resolve(); resolve();
}); });
@ -418,6 +451,7 @@ describe("mc-server", function() {
'online-mode': false, 'online-mode': false,
kickTimeout: 100, kickTimeout: 100,
checkTimeoutInterval: 10, checkTimeoutInterval: 10,
version: version.majorVersion
}); });
var count = 2; var count = 2;
server.on('connection', function(client) { server.on('connection', function(client) {
@ -435,6 +469,7 @@ describe("mc-server", function() {
host: '127.0.0.1', host: '127.0.0.1',
port: 25565, port: 25565,
keepAlive: false, keepAlive: false,
version: version.majorVersion
}); });
client.on('end', function() { client.on('end', function() {
resolve(); resolve();
@ -450,17 +485,21 @@ describe("mc-server", function() {
'online-mode': false, 'online-mode': false,
motd: 'test1234', motd: 'test1234',
'max-players': 120, 'max-players': 120,
version: version.majorVersion
}); });
server.on('listening', function() { server.on('listening', function() {
mc.ping({host: '127.0.0.1'}, function(err, results) { mc.ping({
host: '127.0.0.1',
version: version.majorVersion
}, function(err, results) {
if(err) return done(err); if(err) return done(err);
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, { assert.deepEqual(results, {
version: { version: {
name: mc.minecraftVersion, name: version.minecraftVersion,
protocol: mc.version protocol: version.version
}, },
players: { players: {
max: 120, max: 120,
@ -475,7 +514,10 @@ describe("mc-server", function() {
server.on('close', done); server.on('close', done);
}); });
it("clients can log in and chat", function(done) { it("clients can log in and chat", function(done) {
var server = mc.createServer({'online-mode': false,}); var server = mc.createServer({
'online-mode': false,
version: version.majorVersion
});
var username = ['player1', 'player2']; var username = ['player1', 'player2'];
var index = 0; var index = 0;
server.on('login', function(client) { server.on('login', function(client) {
@ -502,7 +544,11 @@ 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', host: '127.0.0.1'}); var player1 = mc.createClient({
username: 'player1',
host: '127.0.0.1',
version: version.majorVersion
});
player1.on('login', function(packet) { player1.on('login', function(packet) {
assert.strictEqual(packet.gameMode, 1); assert.strictEqual(packet.gameMode, 1);
assert.strictEqual(packet.levelType, 'default'); assert.strictEqual(packet.levelType, 'default');
@ -530,7 +576,11 @@ describe("mc-server", function() {
}); });
player2.write('chat', {message: "hi"}); player2.write('chat', {message: "hi"});
}); });
var player2 = mc.createClient({username: 'player2', host: '127.0.0.1'}); var player2 = mc.createClient({
username: 'player2',
host: '127.0.0.1',
version: version.majorVersion
});
}); });
}); });
@ -546,7 +596,9 @@ describe("mc-server", function() {
}); });
it("kicks clients when invalid credentials", function(done) { it("kicks clients when invalid credentials", function(done) {
this.timeout(10000); this.timeout(10000);
var server = mc.createServer(); var server = mc.createServer({
version: version.majorVersion
});
var count = 4; var count = 4;
server.on('connection', function(client) { server.on('connection', function(client) {
client.on('end', function(reason) { client.on('end', function(reason) {
@ -561,7 +613,8 @@ describe("mc-server", function() {
resolve(); resolve();
var client = mc.createClient({ var client = mc.createClient({
username: 'lalalal', username: 'lalalal',
host: "127.0.0.1" host: "127.0.0.1",
version: version.majorVersion
}); });
client.on('end', function() { client.on('end', function() {
resolve(); resolve();
@ -573,7 +626,10 @@ describe("mc-server", function() {
} }
}); });
it("gives correct reason for kicking clients when shutting down", function(done) { it("gives correct reason for kicking clients when shutting down", function(done) {
var server = mc.createServer({'online-mode': false,}); var server = mc.createServer({
'online-mode': false,
version: version.majorVersion
});
var count = 2; var count = 2;
server.on('login', function(client) { server.on('login', function(client) {
client.on('end', function(reason) { client.on('end', function(reason) {
@ -594,7 +650,11 @@ describe("mc-server", function() {
resolve(); resolve();
}); });
server.on('listening', function() { server.on('listening', function() {
var client = mc.createClient({username: 'lalalal', host: '127.0.0.1'}); var client = mc.createClient({
username: 'lalalal',
host: '127.0.0.1',
version: version.majorVersion
});
client.on('login', function() { client.on('login', function() {
server.close(); server.close();
}); });
@ -605,3 +665,4 @@ describe("mc-server", function() {
} }
}); });
}); });
})