minecraft.js
Version:
Minecraft data serialization/deserialization and networking
1,110 lines (991 loc) • 27.3 kB
JavaScript
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;