UNPKG

minecraft.js

Version:

Minecraft data serialization/deserialization and networking

1,110 lines (991 loc) 27.3 kB
var constants = require(__dirname + '/../constants.js'); var protocol = { 0x00: [{type: 'int', name: 'keepAliveId'}], 0x01: { toServer: [ {type: 'int', name: 'protocolVersion'}, {type: 'string', name: 'username'}, {type: 'string', name: ''}, {type: 'int', name: ''}, {type: 'int', name: ''}, {type: 'byte', name: ''}, {type: 'byte', name: ''}, {type: 'ubyte', name: ''} ], toClient:[ {type: 'int', name: 'entityId'}, {type: 'string', name: ''}, {type: 'string', name: 'levelType'}, {type: 'int', name: 'serverMode'}, {type: 'int', name: 'dimension'}, {type: 'byte', name: 'difficulty'}, {type: 'byte', name: ''}, {type: 'ubyte', name: 'maxPlayers'} ]}, 0x02: { toServer: [{type: 'string', name: 'usernameAndHost'}], toClient: [{type: 'string', name: 'connectionHash'}] }, 0x03: [{type: 'string', name: 'message'}], 0x04: [{type: 'long', name: 'time'}], 0x05: [ {type: 'int', name: 'entityId'}, {type: 'short', name: 'slot'}, {type: 'short', name: 'itemId'}, {type: 'short', name: 'damage'} ], 0x06: [ {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'} ], 0x07: [ {type: 'int', name: 'user'}, {type: 'int', name: 'target'}, {type: 'bool', name: 'leftClick'} ], 0x08: [ {type: 'short', name: 'health'}, {type: 'short', name: 'food'}, {type: 'float', name: 'foodSaturation'} ], 0x09: [ {type: 'int', name: 'dimension'}, {type: 'byte', name: 'difficulty'}, {type: 'byte', name: 'creativeMode'}, {type: 'short', name: 'worldHeight'}, {type: 'string', name: 'levelType'} ], 0x0a: [{type: 'bool', name: 'onGround'}], 0x0b: [ {type: 'double', name: 'x'}, {type: 'double', name: 'y'}, {type: 'double', name: 'stance'}, {type: 'double', name: 'z'}, {type: 'bool', name: 'onGround'} ], 0x0c: [ {type: 'float', name: 'yaw'}, {type: 'float', name: 'pitch'}, {type: 'bool', name: 'onGround'} ], 0x0d: {toServer:[ {type: 'double', name: 'x'}, {type: 'double', name: 'y'}, {type: 'double', name: 'stance'}, {type: 'double', name: 'z'}, {type: 'float', name: 'yaw'}, {type: 'float', name: 'pitch'}, {type: 'bool', name: 'onGround'} ], toClient:[ {type: 'double', name: 'x'}, {type: 'double', name: 'stance'}, {type: 'double', name: 'y'}, {type: 'double', name: 'z'}, {type: 'float', name: 'yaw'}, {type: 'float', name: 'pitch'}, {type: 'bool', name: 'onGround'} ]}, 0x0e: [ {type: 'byte', name: 'status'}, {type: 'int', name: 'x'}, {type: 'byte', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'face'} ], 0x0f: [ {type: 'int', name: 'x'}, {type: 'byte', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'direction'}, {type: 'slot', name: 'heldItem'} ], 0x10: [{type: 'short', name: 'slotId'}], 0x11: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'inBed'}, {type: 'int', name: 'x'}, {type: 'byte', name: 'y'}, {type: 'int', name: 'z'} ], 0x12: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'animation'} ], 0x13: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'actionId'} ], 0x14: [ {type: 'int', name: 'entityId'}, {type: 'string', name: 'name'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'}, {type: 'short', name: 'currentItem'} ], 0x15: [ {type: 'int', name: 'entityId'}, {type: 'short', name: 'item'}, {type: 'byte', name: 'count'}, {type: 'short', name: 'damage'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'}, {type: 'byte', name: 'roll'} ], 0x16: [ {type: 'int', name: 'collectedId'}, {type: 'int', name: 'collectorId'} ], 0x17: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'type'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'thrower', name: 'thrower'} ], 0x18: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'type'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'}, {type: 'byte', name: 'headYaw'}, {type: 'meta', name: 'metadata'} ], 0x19: [ {type: 'int', name: 'entityId'}, {type: 'string', name: 'motive'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'int', name: 'direction'} ], 0x1a: [ {type: 'int', name: 'entityId'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'short', name: 'count'} ], 0x1c: [ {type: 'int', name: 'entityId'}, {type: 'short', name: 'x'}, {type: 'short', name: 'y'}, {type: 'short', name: 'z'} ], 0x1d: [{type: 'int', name: 'entityId'}], 0x1e: [{type: 'int', name: 'entityId'}], 0x1f: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'dx'}, {type: 'byte', name: 'dy'}, {type: 'byte', name: 'dz'} ], 0x20: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'} ], 0x21: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'dx'}, {type: 'byte', name: 'dy'}, {type: 'byte', name: 'dz'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'} ], 0x22: [ {type: 'int', name: 'entityId'}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'yaw'}, {type: 'byte', name: 'pitch'} ], 0x23: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'headYaw'} ], 0x26: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'status'} ], 0x27: [ {type: 'int', name: 'entityId'}, {type: 'int', name: 'vehicleId'} ], 0x28: [ {type: 'int', name: 'entityId'}, {type: 'meta', name: 'metadata'} ], 0x29: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'effectId'}, {type: 'byte', name: 'amplifier'}, {type: 'short', name: 'duration'} ], 0x2a: [ {type: 'int', name: 'entityId'}, {type: 'byte', name: 'effectId'}, ], 0x2b: [ {type: 'float', name: 'experienceBar'}, {type: 'short', name: 'level'}, {type: 'short', name: 'totalExperience'} ], 0x32: [ {type: 'int', name: 'x'}, {type: 'int', name: 'z'}, {type: 'bool', name: 'mode'} ], 0x33: [ {type: 'int', name: 'x'}, {type: 'int', name: 'z'}, {type: 'bool', name: 'groundUp'}, {type: 'short', name: 'bitMap'}, {type: 'short', name: 'addBitMap'}, {type: 'array64', name: 'blocks'} ], 0x34: [ {type: 'int', name: 'chunkX'}, {type: 'int', name: 'chunkZ'}, {type: 'short', name: 'recordCount'}, {type: 'array32', name: 'blocks'} ], 0x35: [ {type: 'int', name: 'x'}, {type: 'byte', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'type'}, {type: 'byte', name: 'metadata'} ], 0x36: [ {type: 'int', name: 'x'}, {type: 'short', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'byte1'}, {type: 'byte', name: 'byte2'} ], 0x3c: [ {type: 'double', name: 'x'}, {type: 'double', name: 'y'}, {type: 'double', name: 'z'}, {type: 'float', name: '?'}, {type: 'recordarray', name: 'records'} ], 0x3d: [ {type: 'int', name: 'effectId'}, {type: 'int', name: 'x'}, {type: 'byte', name: 'y'}, {type: 'int', name: 'z'}, {type: 'int', name: 'data'} ], 0x46: [ {type: 'byte', name: 'reason'}, {type: 'byte', name: 'gameMode'} ], 0x47: [ {type: 'int', name: 'entityId'}, {type: 'bool', name: ''}, {type: 'int', name: 'x'}, {type: 'int', name: 'y'}, {type: 'int', name: 'z'} ], 0x64: [ {type: 'byte', name: 'windowId'}, {type: 'byte', name: 'inventoryType'}, {type: 'string', name: 'windowTitle'}, {type: 'byte', name: 'slots'} ], 0x65: [{type: 'byte', name: 'windowId'}] , 0x66: [ {type: 'byte', name: 'windowId'}, {type: 'short', name: 'slot'}, {type: 'byte', name: 'rightClick'}, {type: 'short', name: 'action'}, {type: 'bool', name: 'shift'}, {type: 'slot', name: 'item'} ], 0x67: [ {type: 'byte', name: 'windowId'}, {type: 'short', name: 'slot'}, {type: 'slot', name: 'data'} ], 0x68: [ {type: 'byte', name: 'windowId'}, {type: 'short', name: 'count'}, {type: 'slotarray', name: 'data'} ], 0x69: [ {type: 'byte', name: 'windowId'}, {type: 'short', name: 'property'}, {type: 'short', name: 'value'} ], 0x6a: [ {type: 'byte', name: 'windowId'}, {type: 'short', name: 'action'}, {type: 'bool', name: 'accepted'} ], 0x6b: [ {type: 'short', name: 'slot'}, {type: 'slot', name: 'item'} ], 0x6c: [ {type: 'byte', name: 'windowId'}, {type: 'byte', name: 'enchantment'} ], 0x82: [ {type: 'int', name: 'x'}, {type: 'short', name: 'y'}, {type: 'int', name: 'z'}, {type: 'string', name: 'text1'}, {type: 'string', name: 'text2'}, {type: 'string', name: 'text3'}, {type: 'string', name: 'text4'} ], 0x83: [ {type: 'short', name: 'type'}, {type: 'short', name: 'itemId'}, {type: 'arrayU8', name: 'text'} ], 0x84: [ {type: 'int', name: 'x'}, {type: 'short', name: 'y'}, {type: 'int', name: 'z'}, {type: 'byte', name: 'action'}, {type: 'int', name: 'custom1'}, {type: 'int', name: 'custom2'}, {type: 'int', name: 'custom3'} ], 0xc8: [ {type: 'int', name: 'statisticId'}, {type: 'byte', name: 'amount'} ], 0xc9: [ {type: 'string', name: 'playerName'}, {type: 'bool', name: 'online'}, {type: 'short', name: 'ping'} ], 0xca: [ {type: 'bool', name: 'invulnerability'}, {type: 'bool', name: 'isFilying'}, {type: 'bool', name: 'canFly'}, {type: 'bool', name: 'instantDestroy'} ], 0xfa: [ {type: 'string', name: 'channel'}, {type: 'array16', name: 'data'} ], 0xfe: [], 0xff: [{type: 'string', name: 'reason'}] }; protocol.get = function(id, toServer) { if(Array.isArray(protocol[id])) { return protocol[id]; } else if(toServer) { return protocol[id].toServer; } else if(typeof protocol[id] !== 'undefined') { return protocol[id].toClient; } }; protocol.datatypes = { BYTE: { size: 1, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeInt8(value, 0); return 1; }, unpack: function(buf) { return { value: buf.readInt8(0), bytes: 1 }; } }, UBYTE: { size: 1, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeUInt8(value, 0); return 1; }, unpack: function(buf) { return { value: buf.readUInt8(0), bytes: 1 }; } }, SHORT: { size: 2, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeInt16BE(value, 0); return 2; }, unpack: function(buf) { return { value: buf.readInt16BE(0), bytes: 2 }; } }, INT: { size: 4, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeInt32BE(value, 0); return 4; }, unpack: function(buf) { return { value: buf.readInt32BE(0), bytes: 4 }; } }, LONG: { size: 8, empty: [0, 0], dynamic: false, pack: function(value, buf) { buf.writeInt32BE(value[0] || 0, 0); buf.writeInt32BE(value[1] || 0, 4); return 8; }, unpack: function(buf) { return { value: [buf.readInt32BE(0), buf.readInt32BE(4)], bytes: 8 }; } }, FLOAT: { size: 4, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeFloatBE(value, 0); return 4; }, unpack: function(buf) { return { value: buf.readFloatBE(0), bytes: 4 }; } }, DOUBLE: { size: 8, empty: 0, dynamic: false, pack: function(value, buf) { buf.writeDoubleBE(value, 0); return 8; }, unpack: function(buf) { return { value: buf.readDoubleBE(0), bytes: 8 }; } }, STRING: { size: 2, empty: '', dynamic: true, getSize: function(data) { var output = 2; if(data) output += data.length * 2; return output; }, pack: function(value, buf) { var offset = 0; buf.writeInt16BE(value.length, offset); offset += 2; for(var j = 0; j < value.length; j++) { if(j > constants.STRING_MAX_LENGTH) break; buf.writeUInt16BE(value.charCodeAt(j), offset); offset += 2; } return offset; }, unpack: function(buf) { var offset = 0; var stringLength = buf.readInt16BE(offset); if(stringLength > constants.STRING_MAX_LENGTH) return false; offset += 2; var value = ''; for(var j = 0; j < stringLength; j++) { value += String.fromCharCode(buf.readUInt16BE(offset)); offset += 2; } return { value: value, bytes: offset }; } }, BOOL: { size: 1, dynamic: false, empty: false, pack: function(value, buf) { if(value === true) buf.writeUInt8(1, 0); else buf.writeUInt8(0, 0); return 1; }, unpack: function(buf) { var value; if(buf.readUInt8(0) == 1) value = true; else value = false; return { value: value, bytes: 1 }; } }, SLOT: { dynamic: true, getSize: function(data) { var output = 2; if( typeof data != 'undefined') { output += 3; } if(items.enchantable.indexOf(blockId) > -1) { output += 2; //TODO: count compressed enchantment NBT } return output; }, pack: function(value, buf) { var offset = 0; if( typeof value != 'undefined') { buf.writeInt16BE(value.id, offset); offset += 2; buf.writeInt8(value.count, offset); offset += 1; buf.writeInt16BE(value.damage, offset); offset += 2; if(items.enchantable.indexOf(blockId) > -1) { buf.writeInt16BE(-1, offset); offset += 2; //TODO: write enchantment data } } else { buf.writeInt16BE(-1, offset); offset += 2; } return offset; }, unpack: function(buf) { var offset = 0; var blockId = buf.readInt16BE(offset); offset += 2; if(blockId == -1) { return { value: undefined, bytes: 2 }; } else { var count = buf.readInt8(offset); offset += 1; var damage = buf.readInt16BE(offset); offset += 2; var item = new Item({ id: blockId, count: count, damage: damage }); if(items.enchantable.indexOf(blockId) > -1) { var arraySize = buf.readInt16BE(offset); offset += 2; if(arraySize > 0) { var array = buf.slice(offset, offset + arraySize); offset += arraySize; var inflated; zlib.inflate(array, function(inflatedData) { inflated = inflatedData; }); while( typeof inflated == 'undefined') { } item.tag.ench = inflated; //TODO: decode NBT data } } return { value: item, bytes: offset }; } } }, THROWER: { dynamic: true, getSize: function(data) { if( typeof data != 'undefined') return 10; return 4; }, pack: function(value, buf) { var offset = 0; if( typeof this.data[i].value != 'undefined') { buf.writeUInt32BE(value.thrower.id, offset); offset += 4; buf.writeUInt16BE(value.thrower.dx, offset); offset += 2; buf.writeUInt16BE(value.thrower.dy, offset); offset += 2; buf.writeUInt16BE(value.thrower.dz, offset); offset += 2; } else { buf.writeUInt32BE(0, offset); offset += 4; } return offset; }, unpack: function(buf) { var offset = 0; var value; var id = buf.readInt32BE(offset); offset += 4; if(id > 0) { value = {}; value.dx = buf.readInt16BE(offset); offset += 2; value.dy = buf.readInt16BE(offset); offset += 2; value.dz = buf.readInt16BE(offset); offset += 2; } else { value = undefined; } return { value: value, bytes: offset }; } }, META: { dynamic: true, empty: 127, getSize: function(data) { //TODO: actually count return 1; }, pack: function(value, buf) { //TODO: actually put data in buf.writeUInt8(127, 0); return 1; }, unpack: function(buf) { var value = []; var offset = 0; var bits = buf.readUInt8(offset); offset += 1; while(bits != 127) { var type = bits >> 5; var id = bits & 0x1f; var subvalue; switch(type) { case 0: subvalue = buf.readInt8(offset); offset += 1; break; case 1: subvalue = buf.readInt16BE(offset); offset += 2; break; case 2: subvalue = buf.readInt32BE(offset); offset += 4; break; case 3: subvalue = buf.readFloatBE(offset); offset += 4; break; case 4: var stringLength = buf.readInt16BE(offset); if(stringLength > constants.STRING_MAX_LENGTH) return false; offset += 2; subvalue = ''; for(var j = 0; j < stringLength; j++) { subvalue += String.fromCharCode(buf.readUInt16BE(offset)); offset += 2; } break; case 5: subvalue = {}; subvalue.id = buf.readInt16BE(offset); offset += 2; subvalue.count = buf.readInt8(offset); offset += 1; subvalue.id = buf.readInt16BE(offset); offset += 2; break; case 6: subvalue = []; for(var j = 0; j < 3; j++) { subvalue.push(buf.readInt32BE(offset)); offset += 4; } break; } value[id] = subvalue; bits = buf.readUInt8(offset); offset += 1; } return { value: value, bytes: offset }; } }, ARRAYU8: { dynamic: true, getSize: function(data) { return 1 + data.length; }, pack: function(value, buf) { buf.writeUInt8(value.length, 0); value.copy(buf, 1); return 1 + value.length; }, unpack: function(buf) { var offset = 0; var value; var arrayLength = buf.readUInt8(offset); offset += 1; value = buf.slice(offset, offset + arrayLength); offset += arrayLength; return { value: value, bytes: offset }; } }, ARRAY16: { dynamic: true, getSize: function(data) { return 2 + data.length; }, pack: function(value, buf) { buf.writeInt16BE(value.length, 0); value.copy(buf, 2); return 2 + value.length; }, unpack: function(buf) { var offset = 0; var value; var arrayLength = buf.readInt16BE(offset); offset += 2; value = buf.slice(offset, offset + arrayLength); offset += arrayLength; return { value: value, bytes: offset }; } }, ARRAY32: { dynamic: true, getSize: function(data) { return 4 + data.length; }, pack: function(value, buf) { buf.writeInt32BE(value.length, 0); value.copy(buf, 4); return 4 + value.length; }, unpack: function(buf) { var offset = 0; var value; var arrayLength = buf.readInt32BE(offset); offset += 4; value = buf.slice(offset, offset + arrayLength); offset += arrayLength; return { value: value, bytes: offset }; } }, ARRAY64: { dynamic: true, getSize: function(data) { return 8 + data.length; }, pack: function(value, buf) { buf.writeInt32BE(value.length, 0); value.copy(buf, 8); return 8 + value.length; }, unpack: function(buf) { var offset = 0; var arrayLength = buf.readInt32BE(offset); offset += 8; var value = buf.slice(offset, offset + arrayLength); offset += arrayLength; return { value: value, bytes: offset }; } }, RECORDARRAY: { dynamic: true, getSize: function(data) { return 4 + data.length * 3; }, pack: function(value, buf) { var offset = 0; buf.writeInt32BE(value.length, offset); offset += 4; for(var j = 0; j < value.length; j++) { buf.writeInt8(value.x, offset); offset += 1; buf.writeInt8(value.y, offset); offset += 1; buf.writeInt8(value.z, offset); offset += 1; } return offset; }, unpack: function(buf) { var offset = 0; var arrayLength = buf.readInt32BE(offset); offset += 4; var value = []; for(var j = 0; j < arrayLength; j++) { subvalue = {}; subvalue.x = buf.readInt8(offset); offset += 1; subvalue.y = buf.readInt8(offset); offset += 1; subvalue.z = buf.readInt8(offset); offset += 1; } return { value: value, bytes: offset }; } }, SLOTARRAY: { dynamic: true, getSize: function(data) { var size = 2; for(var j = 0; j < data.length; j++) { size += Packet.prototype.types['SLOT'].getSize(data[j]); } return size; }, pack: function(value, buf) { var offset = 0; buf.writeInt16BE(value.length, offset); offset += 2; for(var j = 0; j < value.length; j++) { offset += Packet.prototype.types['SLOT'].pack(value[j], buf.slice(offset)); } return offset; }, unpack: function(buf) { var value = []; var offset = 0; var arrayLength = buf.readInt16BE(offset); offset += 2; for(var j = 0; j < arrayLength; j++) { var unpack = Packet.prototype.types['SLOT'].unpack(buf.slice(offset)); offset += unpack.bytes; value.push(unpack.value); } return { value: value, bytes: offset }; } } }; module.exports = protocol;