diff --git a/package.json b/package.json index 8020a6d..0190e52 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "prepublish": "gulp", - "test": "mocha --reporter spec" + "test": "mocha --require source-map-support/register --reporter spec" }, "keywords": [ "minecraft", @@ -28,14 +28,15 @@ }, "browser": "browser.js", "devDependencies": { + "batch": "~0.3.1", "gulp": "^3.8.11", - "gulp-plumber": "^1.0.1", "gulp-babel": "^5.1.0", + "gulp-plumber": "^1.0.1", "gulp-sourcemaps": "^1.3.0", "mkdirp": "~0.3.4", "mocha": "~1.8.2", "rimraf": "~2.1.1", - "batch": "~0.3.1", + "source-map-support": "^0.3.2", "zfill": "0.0.1" }, "dependencies": { @@ -46,7 +47,7 @@ "readable-stream": "^1.1.0", "superagent": "~0.10.0", "ursa-purejs": "0.0.3", - "minecraft-data": "0.5.1" + "minecraft-data": "0.6.0" }, "optionalDependencies": { "ursa": "~0.8.0" diff --git a/src/datatypes/conditional.js b/src/datatypes/conditional.js index e7e07b1..2bb82b3 100644 --- a/src/datatypes/conditional.js +++ b/src/datatypes/conditional.js @@ -7,19 +7,19 @@ module.exports = { function readCondition(buffer, offset, typeArgs, rootNode) { if(!evalCondition(typeArgs, rootNode)) return {value: null, size: 0}; - return this.read(buffer, offset, {type: typeArgs.type, typeArgs: typeArgs.typeArgs}, rootNode); + return this.read(buffer, offset, typeArgs.type, rootNode); } function writeCondition(value, buffer, offset, typeArgs, rootNode) { if(!evalCondition(typeArgs, rootNode)) return offset; - return this.write(value, buffer, offset, {type: typeArgs.type, typeArgs: typeArgs.typeArgs}, rootNode); + return this.write(value, buffer, offset, typeArgs.type, rootNode); } -function sizeOfCondition(value, fieldInfo, rootNode) { - if(!evalCondition(fieldInfo, rootNode)) +function sizeOfCondition(value, typeArgs, rootNode) { + if(!evalCondition(typeArgs, rootNode)) return 0; - return this.sizeOf(value, fieldInfo, rootNode); + return this.sizeOf(value, typeArgs.type, rootNode); } diff --git a/src/datatypes/minecraft.js b/src/datatypes/minecraft.js index 06e7e26..54979a2 100644 --- a/src/datatypes/minecraft.js +++ b/src/datatypes/minecraft.js @@ -9,7 +9,7 @@ module.exports = { 'position': [readPosition, writePosition, 8], 'slot': [readSlot, writeSlot, sizeOfSlot], 'nbt': [readNbt, utils.buffer[1], utils.buffer[2]], - 'restBuffer': [readRestBuffer, utils.buffer[1], utils.buffer[2]], + 'restBuffer': [readRestBuffer, writeRestBuffer, sizeOfRestBuffer], 'entityMetadata': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata] }; @@ -131,13 +131,21 @@ function sizeOfNbt(value) { } -function readRestBuffer(buffer, offset, typeArgs, rootNode) { +function readRestBuffer(buffer, offset) { return { value: buffer.slice(offset), size: buffer.length - offset }; } +function writeRestBuffer(value, buffer, offset) { + value.copy(buffer, offset); + return offset + value.length; +} + +function sizeOfRestBuffer(value) { + return value.length; +} var entityMetadataTypes = { 0: {type: 'byte'}, diff --git a/src/datatypes/structures.js b/src/datatypes/structures.js index 513e658..2aac73b 100644 --- a/src/datatypes/structures.js +++ b/src/datatypes/structures.js @@ -33,7 +33,7 @@ function readArray(buffer, offset, typeArgs, rootNode) { } else // TODO : broken schema, should probably error out. count = 0; for(var i = 0; i < count; i++) { - var readResults = this.read(buffer, offset, {type: typeArgs.type, typeArgs: typeArgs.typeArgs}, rootNode); + var readResults = this.read(buffer, offset, typeArgs.type, rootNode); results.size += readResults.size; offset += readResults.size; results.value.push(readResults.value); @@ -48,7 +48,7 @@ function writeArray(value, buffer, offset, typeArgs, rootNode) { } else if (typeof typeArgs.count === "undefined") { // Broken schema, should probably error out } for(var index in value) { - offset = this.write(value[index], buffer, offset, {type: typeArgs.type, typeArgs: typeArgs.typeArgs}, rootNode); + offset = this.write(value[index], buffer, offset, typeArgs.type, rootNode); } return offset; } @@ -60,7 +60,7 @@ function sizeOfArray(value, typeArgs, rootNode) { size = this.sizeOf(value.length, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); } for(var index in value) { - size += this.sizeOf(value[index], {type: typeArgs.type, typeArgs: typeArgs.typeArgs}, rootNode); + size += this.sizeOf(value[index], typeArgs.type, rootNode); } return size; } @@ -76,58 +76,51 @@ function readContainer(buffer, offset, typeArgs, rootNode) { // TODO : what I do inside of roblabla/Protocols is have each "frame" create a new empty slate with just a "super" object pointing to the parent. var backupThis = rootNode.this; rootNode.this = results.value; - for(var index in typeArgs.fields) { - var readResults = this.read(buffer, offset, typeArgs.fields[index], rootNode); + for(var index in typeArgs) { + var readResults = this.read(buffer, offset, typeArgs[index].type, rootNode); if(readResults == null || readResults.value == null) { continue; } results.size += readResults.size; offset += readResults.size; - results.value[typeArgs.fields[index].name] = readResults.value; + results.value[typeArgs[index].name] = readResults.value; } rootNode.this = backupThis; return results; } function writeContainer(value, buffer, offset, typeArgs, rootNode) { - var context = value.this ? value.this : value; var backupThis = rootNode.this; rootNode.this = value; - for(var index in typeArgs.fields) { - if(!context.hasOwnProperty(typeArgs.fields[index].name) && typeArgs.fields[index].type != "count" && - (typeArgs.fields[index].type != "condition" || evalCondition(typeArgs.fields[index].typeArgs, rootNode))) { - debug(new Error("Missing Property " + typeArgs.fields[index].name).stack); - console.log(context); - } - offset = this.write(context[typeArgs.fields[index].name], buffer, offset, typeArgs.fields[index], rootNode); + for(var index in typeArgs) { + offset = this.write(value[typeArgs[index].name], buffer, offset, typeArgs[index].type, rootNode); } - rootNode.this = backupThis;; + rootNode.this = backupThis; return offset; } function sizeOfContainer(value, typeArgs, rootNode) { var size = 0; - var context = value.this ? value.this : value; var backupThis = rootNode.this; rootNode.this = value; - for(var index in typeArgs.fields) { - size += this.sizeOf(context[typeArgs.fields[index].name], typeArgs.fields[index], rootNode); + for(var index in typeArgs) { + size += this.sizeOf(value[typeArgs[index].name], typeArgs[index].type, rootNode); } rootNode.this = backupThis; return size; } function readCount(buffer, offset, typeArgs, rootNode) { - return this.read(buffer, offset, {type: typeArgs.type}, rootNode); + return this.read(buffer, offset, typeArgs.type, rootNode); } function writeCount(value, buffer, offset, typeArgs, rootNode) { // Actually gets the required field, and writes its length. Value is unused. // TODO : a bit hackityhack. - return this.write(getField(typeArgs.countFor, rootNode).length, buffer, offset, {type: typeArgs.type}, rootNode); + return this.write(getField(typeArgs.countFor, rootNode).length, buffer, offset, typeArgs.type, rootNode); } function sizeOfCount(value, typeArgs, rootNode) { // TODO : should I use value or getField().length ? - return this.sizeOf(getField(typeArgs.countFor, rootNode).length, {type: typeArgs.type}, rootNode); + return this.sizeOf(getField(typeArgs.countFor, rootNode).length, typeArgs.type, rootNode); } diff --git a/src/protocol.js b/src/protocol.js index d8f916b..50327d6 100644 --- a/src/protocol.js +++ b/src/protocol.js @@ -1,3 +1,5 @@ +var { getFieldInfo } = require('./utils'); + function NMProtocols() { this.types = {}; } @@ -13,15 +15,15 @@ NMProtocols.prototype.addTypes = function(types) { }); }; -NMProtocols.prototype.read = function(buffer, cursor, fieldInfo, rootNodes) { +NMProtocols.prototype.read = function(buffer, cursor, _fieldInfo, rootNodes) { + let fieldInfo = getFieldInfo(_fieldInfo); var type = this.types[fieldInfo.type]; if(!type) { return { error: new Error("missing data type: " + fieldInfo.type) }; } - var typeArgs = fieldInfo.typeArgs || {}; - var readResults = type[0].call(this, buffer, cursor, typeArgs, rootNodes); + var readResults = type[0].call(this, buffer, cursor, fieldInfo.typeArgs, rootNodes); if(readResults == null) { throw new Error("Reader returned null : " + JSON.stringify(fieldInfo)); } @@ -29,25 +31,25 @@ NMProtocols.prototype.read = function(buffer, cursor, fieldInfo, rootNodes) { return readResults; }; -NMProtocols.prototype.write = function(value, buffer, offset, fieldInfo, rootNode) { +NMProtocols.prototype.write = function(value, buffer, offset, _fieldInfo, rootNode) { + let fieldInfo = getFieldInfo(_fieldInfo); var type = this.types[fieldInfo.type]; if(!type) { return { error: new Error("missing data type: " + fieldInfo.type) }; } - var typeArgs = fieldInfo.typeArgs || {}; - return type[1].call(this, value, buffer, offset, typeArgs, rootNode); + return type[1].call(this, value, buffer, offset, fieldInfo.typeArgs, rootNode); }; -NMProtocols.prototype.sizeOf = function(value, fieldInfo, rootNode) { +NMProtocols.prototype.sizeOf = function(value, _fieldInfo, rootNode) { + let fieldInfo = getFieldInfo(_fieldInfo); var type = this.types[fieldInfo.type]; if(!type) { throw new Error("missing data type: " + fieldInfo.type); } if(typeof type[2] === 'function') { - var typeArgs = fieldInfo.typeArgs || {}; - return type[2].call(this, value, typeArgs, rootNode); + return type[2].call(this, value, fieldInfo.typeArgs, rootNode); } else { return type[2]; } diff --git a/src/transforms/serializer.js b/src/transforms/serializer.js index b329f42..181d462 100644 --- a/src/transforms/serializer.js +++ b/src/transforms/serializer.js @@ -3,6 +3,7 @@ var protocol = require("../protocol"); var Transform = require("readable-stream").Transform; var debug = require("../debug"); var assert = require('assert'); +var { getFieldInfo } = require('../utils'); module.exports.createSerializer = function(obj) { return new Serializer(obj); @@ -73,7 +74,7 @@ function createPacketBuffer(packetId, state, params, isServer) { assert.notEqual(packet, null); packet.forEach(function(fieldInfo) { try { - length += proto.sizeOf(params[fieldInfo.name], fieldInfo, params); + length += proto.sizeOf(params[fieldInfo.name], fieldInfo.type, params); } catch(e) { console.log("fieldInfo : " + JSON.stringify(fieldInfo)); console.log("params : " + JSON.stringify(params)); @@ -90,7 +91,7 @@ function createPacketBuffer(packetId, state, params, isServer) { // TODO : A better check is probably needed if(typeof value === "undefined" && fieldInfo.type != "count" && (fieldInfo.type != "condition" || evalCondition(fieldInfo.typeArgs, params))) debug(new Error("Missing Property " + fieldInfo.name).stack); - offset = proto.write(value, buffer, offset, fieldInfo, params); + offset = proto.write(value, buffer, offset, fieldInfo.type, params); }); return buffer; } @@ -109,9 +110,7 @@ function get(packetId, state, toServer) { // By default, parse every packets. function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": true}) { var cursor = 0; - var packetIdField = utils.varint[0](buffer, cursor); - var packetId = packetIdField.value; - cursor += packetIdField.size; + var { value: packetId, size: cursor } = utils.varint[0](buffer, cursor); var results = {id: packetId, state: state}; // Only parse the packet if there is a need for it, AKA if there is a listener attached to it @@ -140,7 +139,7 @@ function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": tr var i, fieldInfo, readResults; for(i = 0; i < packetInfo.length; ++i) { fieldInfo = packetInfo[i]; - readResults = proto.read(buffer, cursor, fieldInfo, results); + readResults = proto.read(buffer, cursor, fieldInfo.type, results); /* A deserializer cannot return null anymore. Besides, proto.read() returns * null when the condition is not fulfilled. if (!!!readResults) { diff --git a/src/utils.js b/src/utils.js index 686542d..3d1dc3b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,6 @@ module.exports = { getField: getField, + getFieldInfo: getFieldInfo, evalCondition: evalCondition }; @@ -12,6 +13,16 @@ function getField(countField, rootNode) { return count; } +function getFieldInfo(fieldInfo) { + if (typeof fieldInfo === "string") + return { type: fieldInfo }; + else if (Array.isArray(fieldInfo)) + return { type: fieldInfo[0], typeArgs: fieldInfo[1] }; + else if (typeof fieldInfo.type === "string") + return fieldInfo; + else + throw new Error("Not a fieldinfo"); +} function evalCondition(condition, field_values) { var field_value_to_test = "this" in condition && condition["this"] ? field_values["this"][condition.field] : field_values[condition.field]; diff --git a/test/test.js b/test/test.js index c35452b..ad3ff76 100644 --- a/test/test.js +++ b/test/test.js @@ -14,6 +14,8 @@ var mc = require('../') , MC_SERVER_JAR = process.env.MC_SERVER_JAR , SURVIVE_TIME = 10000 , MC_SERVER_PATH = path.join(__dirname, 'server') + , getFieldInfo = require('../dist/utils').getFieldInfo + , evalCondition = require('../dist/utils').evalCondition ; var defaultServerProps = { @@ -60,25 +62,29 @@ var values = { 'ubyte': 8, 'string': "hi hi this is my client string", 'buffer': new Buffer(8), - 'array': function(typeArgs) { + 'array': function(_typeArgs, packet) { + var typeArgs = getFieldInfo(_typeArgs.type) if(typeof values[typeArgs.type] === "undefined") { throw new Error("No data type for " + typeArgs.type); } if(typeof values[typeArgs.type] === "function") { - return [values[typeArgs.type](typeArgs.typeArgs)]; + return [values[typeArgs.type](typeArgs.typeArgs, packet)]; } return [values[typeArgs.type]]; }, - 'container': function(typeArgs) { + 'container': function(typeArgs, packet) { var results = {}; - for(var index in typeArgs.fields) { - if(typeof values[typeArgs.fields[index].type] === "undefined") { - throw new Error("No data type for " + typeArgs.fields[index].type); + for(var index in typeArgs) { + if(typeof values[getFieldInfo(typeArgs[index].type).type] === "undefined") { + throw new Error("No data type for " + typeArgs[index].type); } - if(typeof values[typeArgs.fields[index].type] === "function") { - results[typeArgs.fields[index].name] = values[typeArgs.fields[index].type](typeArgs.fields[index].typeArgs); + if(typeof values[getFieldInfo(typeArgs[index].type).type] === "function") { + var backupThis = packet.this; + packet.this = results; + results[typeArgs[index].name] = values[getFieldInfo(typeArgs[index].type).type](getFieldInfo(typeArgs[index].type).typeArgs, packet); + packet.this = backupThis; } else { - results[typeArgs.fields[index].name] = values[typeArgs.fields[index].type]; + results[typeArgs[index].name] = values[getFieldInfo(typeArgs[index].type).type]; } } return results; @@ -121,10 +127,13 @@ var values = { 'UUID': "00112233-4455-6677-8899-aabbccddeeff", 'position': {x: 12, y: 332, z: 4382821}, 'restBuffer': new Buffer(0), - 'condition': function(typeArgs) { - // check whether this should return undefined if needed instead ? (using evalCondition) - // probably not since we only send values that respect the condition - return values[typeArgs.type]; + 'condition': function(typeArgs, packet) { + if (evalCondition(typeArgs, packet)) { + if (typeof values[getFieldInfo(typeArgs.type).type] === "function") + return values[getFieldInfo(typeArgs.type).type](getFieldInfo(typeArgs.type).typeArgs, packet); + else + return values[getFieldInfo(typeArgs.type).type]; + } } }; @@ -179,22 +188,25 @@ describe("packets", function() { // empty object uses default values var packet = {}; packetInfo.forEach(function(field) { - if(field.type !== "condition" || mc.evalCondition(field.typeArgs, packet)) { - var fieldVal = values[field.type]; - if(typeof fieldVal === "undefined") { - throw new Error("No value for type " + field.type); - } - if(typeof fieldVal === "function") { - fieldVal = fieldVal(field.typeArgs); - } - packet[field.name] = fieldVal; + var fieldVal = values[getFieldInfo(field.type).type]; + if(typeof fieldVal === "undefined") { + throw new Error("No value for type " + field.type); } + if(typeof fieldVal === "function") { + fieldVal = fieldVal(getFieldInfo(field.type).typeArgs, packet); + } + packet[field.name] = fieldVal; }); if(toServer) { serverClient.once([state, packetId], function(receivedPacket) { delete receivedPacket.id; delete receivedPacket.state; + try { assertPacketsMatch(packet, receivedPacket); + } catch (e) { + console.log(packet, receivedPacket); + throw e; + } done(); }); client.write(packetId, packet); @@ -215,7 +227,8 @@ describe("packets", function() { }); var field; for(field in p1) { - assert.ok(field in p2, "field " + field + " missing in p2, in p1 it has value " + JSON.stringify(p1[field])); + if (p1[field] !== undefined) + assert.ok(field in p2, "field " + field + " missing in p2, in p1 it has value " + JSON.stringify(p1[field])); } for(field in p2) { assert.ok(field in p1, "field " + field + " missing in p1, in p2 it has value " + JSON.stringify(p2[field]));