node-minecraft-protocol/lib/protocol.js

1574 lines
55 KiB
JavaScript
Raw Normal View History

var assert = require('assert');
var util = require('util');
var zlib = require('zlib');
2013-01-03 23:14:19 -05:00
var STRING_MAX_LENGTH = 240;
var SRV_STRING_MAX_LENGTH = 32767;
2013-01-03 23:14:19 -05:00
// This is really just for the client.
var states = {
"HANDSHAKING": "handshaking",
"STATUS": "status",
"LOGIN": "login",
"PLAY": "play"
}
2013-01-03 23:14:19 -05:00
var packets = {
2014-01-14 00:56:56 -05:00
handshaking: {
toClient: {},
2014-12-30 13:23:58 -05:00
toServer: {
set_protocol: {id: 0x00, fields: [
{ name: "protocolVersion", type: "varint" },
{ name: "serverHost", type: "string" },
{ name: "serverPort", type: "ushort" },
{ name: "nextState", type: "varint" }
2014-01-14 00:56:56 -05:00
]}
},
},
2014-01-14 00:56:56 -05:00
// TODO : protocollib names aren't the best around here
2014-01-14 00:56:56 -05:00
status: {
toClient: {
server_info: {id: 0x00, fields: [
{ name: "response", type: "ustring" }
2014-01-14 00:56:56 -05:00
]},
ping: {id: 0x01, fields: [
{ name: "time", type: "long" }
2014-01-14 00:56:56 -05:00
]}
},
2014-01-14 00:56:56 -05:00
toServer: {
ping_start: {id: 0x00, fields: []},
ping: {id: 0x01, fields: [
{ name: "time", type: "long" }
2014-01-14 00:56:56 -05:00
]}
}
2013-01-03 23:14:19 -05:00
},
2014-01-14 00:56:56 -05:00
login: {
toClient: {
disconnect: {id: 0x00, fields: [
{ name: "reason", type: "string" }
2014-01-14 00:56:56 -05:00
]},
encryption_begin: {id: 0x01, fields: [
{ name: "serverId", type: "string" },
{ name: "publicKeyLength", type: "count", typeArgs: { type: "varint", countFor: "publicKey" } },
{ name: "publicKey", type: "buffer", typeArgs: { count: "publicKeyLength" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "varint", countFor: "verifyToken" } },
{ name: "verifyToken", type: "buffer", typeArgs: { count: "verifyTokenLength" } },
2014-01-14 00:56:56 -05:00
]},
success: {id: 0x02, fields: [
{ name: "uuid", type: "string" },
{ name: "username", type: "string" }
]},
compress: { id: 0x03, fields: [
{ name: "threshold", type: "varint"}
2014-01-14 00:56:56 -05:00
]}
},
2014-01-14 00:56:56 -05:00
toServer: {
login_start: {id: 0x00, fields: [
{ name: "username", type: "string" }
2014-01-14 00:56:56 -05:00
]},
encryption_begin: {id: 0x01, fields: [
{ name: "sharedSecretLength", type: "count", typeArgs: { type: "varint", countFor: "sharedSecret" } },
{ name: "sharedSecret", type: "buffer", typeArgs: { count: "sharedSecretLength" } },
{ name: "verifyTokenLength", type: "count", typeArgs: { type: "varint", countFor: "verifyToken" } },
{ name: "verifyToken", type: "buffer", typeArgs: { count: "verifyTokenLength" } },
2014-01-14 00:56:56 -05:00
]}
}
},
2014-01-14 00:56:56 -05:00
play: {
toClient: {
keep_alive: {id: 0x00, fields: [
{ name: "keepAliveId", type: "varint" },
2014-01-14 00:56:56 -05:00
]},
login: {id: 0x01, fields: [
{ name: "entityId", type: "int" },
{ name: "gameMode", type: "ubyte" },
{ name: "dimension", type: "byte" },
{ name: "difficulty", type: "ubyte" },
{ name: "maxPlayers", type: "ubyte" },
{ name: "levelType", type: "string" },
2014-12-30 13:23:58 -05:00
{ name: "reducedDebugInfo", type: "bool"}
2014-01-14 00:56:56 -05:00
]},
chat: {id: 0x02, fields: [
{ name: "message", type: "ustring" },
2014-12-30 13:23:58 -05:00
{ name: "position", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
update_time: {id: 0x03, fields: [
{ name: "age", type: "long" },
{ name: "time", type: "long" },
2014-01-14 00:56:56 -05:00
]},
entity_equipment: {id: 0x04, fields: [
{ name: "entityId", type: "varint" },
{ name: "slot", type: "short" },
{ name: "item", type: "slot" }
2014-01-14 00:56:56 -05:00
]},
spawn_position: {id: 0x05, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" } /* TODO: Implement position */
2014-01-14 00:56:56 -05:00
]},
update_health: {id: 0x06, fields: [
{ name: "health", type: "float" },
2014-12-30 13:23:58 -05:00
{ name: "food", type: "varint" },
{ name: "foodSaturation", type: "float" }
2014-01-14 00:56:56 -05:00
]},
respawn: {id: 0x07, fields: [
{ name: "dimension", type: "int" },
{ name: "difficulty", type: "ubyte" },
{ name: "gamemode", type: "ubyte" },
{ name: "levelType", type: "string" }
2014-01-14 00:56:56 -05:00
]},
position: {id: 0x08, fields: [
{ name: "x", type: "double" },
{ name: "y", type: "double" },
{ name: "z", type: "double" },
{ name: "yaw", type: "float" },
{ name: "pitch", type: "float" },
2014-12-30 13:23:58 -05:00
{ name: "flags", type: "byte" /* <Dinnerbone> It's a bitfield, X/Y/Z/Y_ROT/X_ROT. If X is set, the x value is relative and not absolute. */}
2014-01-14 00:56:56 -05:00
]},
held_item_slot: {id: 0x09, fields: [
{ name: "slot", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
bed: {id: 0x0a, fields: [
{ name: "entityId", type: "int" },
{ name: "location", type: "position" }
2014-01-14 00:56:56 -05:00
]},
animation: {id: 0x0b, fields: [
{ name: "entityId", type: "varint" },
{ name: "animation", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
named_entity_spawn: {id: 0x0c, fields: [
{ name: "entityId", type: "varint" },
2014-12-30 13:23:58 -05:00
{ name: "playerUUID", type: "UUID"},
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
{ name: "yaw", type: "byte" },
{ name: "pitch", type: "byte" },
{ name: "currentItem", type: "short" },
{ name: "metadata", type: "entityMetadata" }
2014-01-14 00:56:56 -05:00
]},
collect: {id: 0x0d, fields: [
{ name: "collectedEntityId", type: "varint" },
{ name: "collectorEntityId", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
spawn_entity: {id: 0x0e, fields: [
{ name: "entityId", type: "varint" },
{ name: "type", type: "byte" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
{ name: "pitch", type: "byte" },
{ name: "yaw", type: "byte" },
{ name: "objectData", type: "container", typeArgs: { fields: [
{ name: "intField", type: "int" },
{ name: "velocityX", type: "short", condition: function(field_values) {
return field_values['this']['intField'] != 0;
}},
{ name: "velocityY", type: "short", condition: function(field_values) {
return field_values['this']['intField'] != 0;
}},
{ name: "velocityZ", type: "short", condition: function(field_values) {
return field_values['this']['intField'] != 0;
}}
2014-12-30 13:23:58 -05:00
]}}
2014-01-14 00:56:56 -05:00
]},
spawn_entity_living: {id: 0x0f, fields: [
{ name: "entityId", type: "varint" },
{ name: "type", type: "ubyte" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
2014-12-30 13:23:58 -05:00
{ name: "yaw", type: "byte" },
{ name: "pitch", type: "byte" },
{ name: "headPitch", type: "byte" },
{ name: "velocityX", type: "short" },
{ name: "velocityY", type: "short" },
{ name: "velocityZ", type: "short" },
{ name: "metadata", type: "entityMetadata" },
2014-01-14 00:56:56 -05:00
]},
spawn_entity_painting: {id: 0x10, fields: [
{ name: "entityId", type: "varint" },
{ name: "title", type: "string" },
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "direction", type: "ubyte" }
2014-01-14 00:56:56 -05:00
]},
spawn_entity_experience_orb: {id: 0x11, fields: [
{ name: "entityId", type: "varint" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
{ name: "count", type: "short" }
2014-01-14 00:56:56 -05:00
]},
entity_velocity: {id: 0x12, fields: [
{ name: "entityId", type: "int" },
{ name: "velocityX", type: "short" },
{ name: "velocityY", type: "short" },
{ name: "velocityZ", type: "short" }
2014-01-14 00:56:56 -05:00
]},
entity_destroy: {id: 0x13, fields: [
2014-12-30 13:23:58 -05:00
{ name: "count", type: "count", typeArgs: { type: "byte", countFor: "entityIds" } }, /* TODO: Might not be correct */
{ name: "entityIds", type: "array", typeArgs: { type: "int", count: "count" } }
2014-01-14 00:56:56 -05:00
]},
entity: {id: 0x14, fields: [
2014-12-30 13:23:58 -05:00
{ name: "entityId", type: "int" }
2014-01-14 00:56:56 -05:00
]},
rel_entity_move: {id: 0x15, fields: [
{ name: "entityId", type: "varint" },
{ name: "dX", type: "byte" },
{ name: "dY", type: "byte" },
2014-12-30 13:23:58 -05:00
{ name: "dZ", type: "byte" },
{ name: "onGround", type: "bool"}
2014-01-14 00:56:56 -05:00
]},
entity_look: {id: 0x16, fields: [
{ name: "entityId", type: "varint" },
{ name: "yaw", type: "byte" },
2014-12-30 13:23:58 -05:00
{ name: "pitch", type: "byte" },
{ name: "onGround", type: "bool"}
2014-01-14 00:56:56 -05:00
]},
entity_move_look: {id: 0x17, fields: [
{ name: "entityId", type: "varint" },
{ name: "dX", type: "byte" },
{ name: "dY", type: "byte" },
{ name: "dZ", type: "byte" },
{ name: "yaw", type: "byte" },
2014-12-30 13:23:58 -05:00
{ name: "pitch", type: "byte" },
{ name: "onGround", type: "bool"}
2014-01-14 00:56:56 -05:00
]},
entity_teleport: {id: 0x18, fields: [
{ name: "entityId", type: "varint" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
{ name: "yaw", type: "byte" },
2014-12-30 13:23:58 -05:00
{ name: "pitch", type: "byte" },
{ name: "onGround", type: "bool"}
2014-01-14 00:56:56 -05:00
]},
entity_head_rotation: {id: 0x19, fields: [
{ name: "entityId", type: "varint" },
{ name: "headYaw", type: "byte" },
2014-01-14 00:56:56 -05:00
]},
entity_status: {id: 0x1a, fields: [
{ name: "entityId", type: "int" },
{ name: "entityStatus", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
attach_entity: {id: 0x1b, fields: [
{ name: "entityId", type: "int" },
{ name: "vehicleId", type: "int" },
{ name: "leash", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
entity_metadata: {id: 0x1c, fields: [
{ name: "entityId", type: "varint" },
{ name: "metadata", type: "entityMetadata" }
2014-01-14 00:56:56 -05:00
]},
entity_effect: {id: 0x1d, fields: [
{ name: "entityId", type: "varint" },
{ name: "effectId", type: "byte" },
{ name: "amplifier", type: "byte" },
2014-12-30 13:23:58 -05:00
{ name: "duration", type: "varint" },
{ name: "hideParticles", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
remove_entity_effect: {id: 0x1e, fields: [
{ name: "entityId", type: "varint" },
{ name: "effectId", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
experience: {id: 0x1f, fields: [
{ name: "experienceBar", type: "float" },
2014-12-30 13:23:58 -05:00
{ name: "level", type: "varint" },
{ name: "totalExperience", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
update_attributes: {id: 0x20, fields: [
{ name: "entityId", type: "varint" },
{ name: "count", type: "count", typeArgs: { type: "int", countFor: "properties" } },
2014-12-30 13:23:58 -05:00
{ name: "properties", type: "array", typeArgs: { count: "count",
type: "container", typeArgs: { fields: [
{ name: "key", type: "string" },
{ name: "value", type: "double" },
{ name: "listLength", type: "count", typeArgs: { type: "short", countFor: "this.modifiers" } },
2014-12-30 13:23:58 -05:00
{ name: "modifiers", type: "array", typeArgs: { count: "this.listLength",
type: "container", typeArgs: { fields: [
{ name: "UUID", type: "UUID" },
{ name: "amount", type: "double" },
{ name: "operation", type: "byte" }
]}}}
]}
}}
2014-01-14 00:56:56 -05:00
]},
map_chunk: {id: 0x21, fields: [
{ name: "x", type: "int" },
{ name: "z", type: "int" },
{ name: "groundUp", type: "bool" },
{ name: "bitMap", type: "ushort" },
{ name: "chunkDataLength", type: "count", typeArgs: { type: "varint", countFor: "chunkData" } },
{ name: "chunkData", type: "buffer", typeArgs: { count: "chunkDataLength" } },
2014-01-14 00:56:56 -05:00
]},
multi_block_change: {id: 0x22, fields: [
{ name: "chunkX", type: "int" },
{ name: "chunkZ", type: "int" },
2014-12-30 13:23:58 -05:00
{ name: "recordCount", type: "varint" },
/* TODO: Is dataLength needed? */
{ name: "dataLength", type: "count", typeArgs: { type: "int", countFor: "data" } },
{ name: "data", type: "buffer", typeArgs: { count: "dataLength" } },
2014-01-14 00:56:56 -05:00
]},
block_change: {id: 0x23, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "type", type: "varint" },
2014-01-14 00:56:56 -05:00
]},
block_action: {id: 0x24, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "byte1", type: "ubyte" },
{ name: "byte2", type: "ubyte" },
{ name: "blockId", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
block_break_animation: {id: 0x25, fields: [
{ name: "entityId", type: "varint" },
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "destroyStage", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
2014-12-31 13:17:02 -05:00
map_chunk_bulk: {id: 0x26, fields: [
{ name: "skyLightSent", type: "bool" },
2014-12-30 13:23:58 -05:00
{ name: "chunkColumnCount", type: "count", typeArgs: { type: "varint", countFor: "meta" } },
2014-12-31 13:17:02 -05:00
{ name: "meta", type: "array", typeArgs: { count: "chunkColumnCount", type: "container", typeArgs: { fields: [
{ name: "x", type: "int" },
{ name: "z", type: "int" },
{ name: "bitMap", type: "ushort" },
2014-12-31 13:17:02 -05:00
]}}},
{ name: "data", type: "restBuffer" }
2014-12-31 13:24:13 -05:00
]},
explosion: {id: 0x27, fields: [
{ name: "x", type: "float" },
{ name: "y", type: "float" },
{ name: "z", type: "float" },
{ name: "radius", type: "float" },
{ name: "count", type: "count", typeArgs: { type: "int", countFor: "affectedBlockOffsets" } },
{ name: "affectedBlockOffsets", type: "array", typeArgs: { count: "count", type: "container", typeArgs: {
fields: [
{ name: "x", type: "byte" },
{ name: "y", type: "byte" },
{ name: "z", type: "byte" }
]
}}},
{ name: "playerMotionX", type: "float" },
{ name: "playerMotionY", type: "float" },
{ name: "playerMotionZ", type: "float" }
2014-01-14 00:56:56 -05:00
]},
world_event: {id: 0x28, fields: [ // TODO : kinda wtf naming there
{ name: "effectId", type: "int" },
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "data", type: "int" },
{ name: "global", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
named_sound_effect: {id: 0x29, fields: [
{ name: "soundName", type: "string" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" },
{ name: "volume", type: "float" },
{ name: "pitch", type: "ubyte" }
2014-01-14 00:56:56 -05:00
]},
world_particles: {id: 0x2a, fields: [
2014-12-30 13:23:58 -05:00
{ name: "particleId", type: "int" },
{ name: "longDistance", type: "bool"},
{ name: "x", type: "float" },
{ name: "y", type: "float" },
{ name: "z", type: "float" },
{ name: "offsetX", type: "float" },
{ name: "offsetY", type: "float" },
{ name: "offsetZ", type: "float" },
2014-12-30 13:23:58 -05:00
{ name: "particleData", type: "float" },
{ name: "particles", type: "int" }
2014-12-30 13:23:58 -05:00
/* TODO: Create an Array of VarInts */
2014-01-14 00:56:56 -05:00
]},
game_state_change: {id: 0x2b, fields: [
{ name: "reason", type: "ubyte" },
{ name: "gameMode", type: "float" }
2014-01-14 00:56:56 -05:00
]},
spawn_entity_weather:{id: 0x2c, fields: [
{ name: "entityId", type: "varint" },
{ name: "type", type: "byte" },
{ name: "x", type: "int" },
{ name: "y", type: "int" },
{ name: "z", type: "int" }
2014-01-14 00:56:56 -05:00
]},
open_window: {id: 0x2d, fields: [
{ name: "windowId", type: "ubyte" },
2014-12-30 13:23:58 -05:00
{ name: "inventoryType", type: "string" },
{ name: "windowTitle", type: "string" },
{ name: "slotCount", type: "ubyte" },
{ name: "entityId", type: "int", condition: function(field_values) {
return field_values['inventoryType'] == 11;
} }
2014-01-14 00:56:56 -05:00
]},
close_window: {id: 0x2e, fields: [
{ name: "windowId", type: "ubyte" }
2014-01-14 00:56:56 -05:00
]},
set_slot: {id: 0x2f, fields: [
2014-12-30 13:23:58 -05:00
{ name: "windowId", type: "byte" },
{ name: "slot", type: "short" },
{ name: "item", type: "slot" }
2014-01-14 00:56:56 -05:00
]},
window_items: {id: 0x30, fields: [
{ name: "windowId", type: "ubyte" },
{ name: "count", type: "count", typeArgs: { type: "short", countFor: "items" } },
{ name: "items", type: "array", typeArgs: { type: "slot", count: "count" } }
2014-01-14 00:56:56 -05:00
]},
2014-12-30 13:23:58 -05:00
craft_progress_bar: {id: 0x31, fields: [ /* TODO: Bad name for this packet imo */
{ name: "windowId", type: "ubyte" },
{ name: "property", type: "short" },
{ name: "value", type: "short" }
2014-01-14 00:56:56 -05:00
]},
transaction:{id: 0x32, fields: [
2014-12-30 13:23:58 -05:00
{ name: "windowId", type: "byte" },
{ name: "action", type: "short" },
{ name: "accepted", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
update_sign: {id: 0x33, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "text1", type: "string" },
{ name: "text2", type: "string" },
{ name: "text3", type: "string" },
{ name: "text4", type: "string" }
2014-01-14 00:56:56 -05:00
]},
2014-12-31 13:17:02 -05:00
map: {id: 0x34, fields: [
{ name: "itemDamage", type: "varint" },
2014-12-31 13:17:02 -05:00
{ name: "scale", type: "byte" },
{ name: "iconLength", type: "count", typeArgs: { type: "varint", countFor: "icons" } },
{ name: "icons", type: "array", typeArgs: { count: "iconLength", type: "container", typeArgs: { fields: [
{ name: "directionAndType", type: "byte" }, // Yeah... that will do
{ name: "x", type: "byte" },
{ name: "y", type: "byte" }
]}}},
{ name: "columns", type: "byte" },
{ name: "rows", type: "byte", condition: function(field_values) {
return field_values["columns"] !== 0;
}},
{ name: "x", type: "byte", condition: function(field_values) {
return field_values["columns"] !== 0;
}},
{ name: "y", type: "byte", condition: function(field_values) {
return field_values["columns"] !== 0;
}},
{ name: "dataLength", type: "count", typeArgs: { countFor: "data", type: "varint" }, condition: function(field_values) {
return field_values["columns"] !== 0;
}},
{ name: "data", type: "buffer", typeArgs: { count: "dataLength" }, condition: function(field_values) {
return field_values["columns"] !== 0;
}},
2014-01-14 00:56:56 -05:00
]},
tile_entity_data:{id: 0x35, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "action", type: "ubyte" },
{ name: "nbtDataLength", type: "count", typeArgs: { type: "short", countFor: "nbtData" } },
{ name: "nbtData", type: "buffer", typeArgs: { count: "nbtDataLength" } },
2014-01-14 00:56:56 -05:00
]},
open_sign_entity: {id: 0x36, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
2014-01-14 00:56:56 -05:00
]},
statistics: {id: 0x37, fields: [
{ name: "count", type: "count", typeArgs: { type: "varint", countFor: "entries" } },
2014-12-30 13:23:58 -05:00
{ name: "entries", type: "array", typeArgs: { count: "count",
type: "container", typeArgs: { fields: [
{ name: "name", type: "string" },
{ name: "value", type: "varint" }
]}
}}
2014-01-14 00:56:56 -05:00
]},
2014-12-31 13:17:02 -05:00
player_info: {id: 0x38, fields: [
{ name: "action", type: "varint" },
{ name: "length", type: "count", typeArgs: { type: "varint", countFor: "data" }},
{ name: "data", type: "array", typeArgs: { count: "length", type: "container", typeArgs: { fields: [
{ name: "UUID", type: "UUID" },
2014-12-31 13:17:02 -05:00
{ name: "name", type: "string", condition: function(field_values) {
return field_values["action"] === 0;
}},
{ name: "propertiesLength", type: "count", condition: function(field_values) {
return field_values["action"] === 0;
}, typeArgs: { countFor: "this.properties", type: "varint" }},
2014-12-31 13:17:02 -05:00
{ name: "properties", type: "array", condition: function(field_values) {
return field_values["action"] === 0;
}, typeArgs: { count: "this.propertiesLength", type: "container", typeArgs: { fields: [
2014-12-31 13:24:13 -05:00
{ name: "name", type: "string" },
{ name: "value", type: "ustring" },
2014-12-31 13:17:02 -05:00
{ name: "isSigned", type: "bool" },
{ name: "signature", type: "ustring", condition: function(field_values) {
2014-12-31 13:17:02 -05:00
return field_values["isSigned"];
2014-12-31 13:24:13 -05:00
}}
]}}},
2014-12-31 13:17:02 -05:00
{ name: "gamemode", type: "varint", condition: function(field_values) {
return field_values["action"] === 0 || field_values["action"] === 1;
}},
{ name: "ping", type: "varint", condition: function(field_values) {
return field_values["action"] === 0 || field_values["action"] === 2;
}},
{ name: "hasDisplayName", type: "bool", condition: function(field_values) {
return field_values["action"] === 0 || field_values["action"] === 3;
}},
{ name: "displayName", type: "string", condition: function(field_values) {
return field_values["hasDisplayName"]; // Returns false if there is no value "hasDisplayName"
}}
]}}}
2014-01-14 00:56:56 -05:00
]},
abilities: {id: 0x39, fields: [
{ name: "flags", type: "byte" },
{ name: "flyingSpeed", type: "float" },
{ name: "walkingSpeed", type: "float" }
2014-01-14 00:56:56 -05:00
]},
tab_complete: {id: 0x3a, fields: [
{ name: "count", type: "count", typeArgs: { type: "varint", countFor: "matches" } },
{ name: "matches", type: "array", typeArgs: { type: "string", count: "count" } }
2014-01-14 00:56:56 -05:00
]},
scoreboard_objective: {id: 0x3b, fields: [
{ name: "name", type: "string" },
2014-12-30 13:23:58 -05:00
{ name: "action", type: "byte" },
{ name: "displayText", type: "string" },
2014-12-30 13:23:58 -05:00
{ name: "type", type: "string"}
2014-01-14 00:56:56 -05:00
]},
2014-12-30 13:23:58 -05:00
scoreboard_score: {id: 0x3c, fields: [ /* TODO: itemName and scoreName may need to be switched */
{ name: "itemName", type: "string" },
2014-12-30 13:23:58 -05:00
{ name: "action", type: "byte" },
{ name: "scoreName", type: "string" },
{ name: "value", type: "int", condition: function(field_values) {
2014-12-30 13:23:58 -05:00
return field_values['action'] != 1;
} }
2014-01-14 00:56:56 -05:00
]},
scoreboard_display_objective: {id: 0x3d, fields: [
{ name: "position", type: "byte" },
{ name: "name", type: "string" }
2014-01-14 00:56:56 -05:00
]},
scoreboard_team: {id: 0x3e, fields: [
{ name: "team", type: "string" },
{ name: "mode", type: "byte" },
{ name: "name", type: "string", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 2;
} },
{ name: "prefix", type: "string", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 2;
} },
{ name: "suffix", type: "string", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 2;
} },
{ name: "friendlyFire", type: "byte", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 2;
} },
2014-04-13 08:25:45 -04:00
{ name: "playerCount", type: "count", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 3 || field_values['mode'] == 4;
2014-04-13 08:25:45 -04:00
}, typeArgs: { type: "short", countFor: "players" } },
{ name: "players", type: "array", condition: function(field_values) {
return field_values['mode'] == 0 || field_values['mode'] == 3 || field_values['mode'] == 4;
}, typeArgs: { type: "string", count: "playerCount" } }
2014-01-14 00:56:56 -05:00
]},
custom_payload: {id: 0x3f, fields: [
{ name: "channel", type: "string" },
{ name: "data", type: "restBuffer" }
2014-01-14 00:56:56 -05:00
]},
kick_disconnect: {id: 0x40, fields: [
{ name: "reason", type: "string" }
2014-12-30 13:23:58 -05:00
]},
2014-12-30 13:59:57 -05:00
difficulty: { id: 0x41, fields: [
2014-12-30 13:23:58 -05:00
{ name: "difficulty", type: "ubyte" }
]},
combat_event: { id: 0x42, fields: [
{ name: "event", type: "varint"},
{ name: "duration", type: "varint", condition: function(field_values) {
return field_values['event'] == 1;
} },
{ name: "entityId", type: "int", condition: function(field_values) {
return field_values['event'] == 1;
} },
{ name: "playerId", type: "varint", condition: function(field_values) {
return field_values['event'] == 2;
} },
{ name: "entityId", type: "int", condition: function(field_values) {
return field_values['event'] == 2;
} },
{ name: "message", type: "string", condition: function(field_values) {
return field_values['event'] == 2;
} }
]},
camera: { id: 0x43, fields: [
{ name: "cameraId", type: "varint" }
]},
world_border: { id: 0x44, fields: [
2014-12-30 13:40:22 -05:00
{ name: "action", type: "varint"},
{ name: "radius", type: "double", condition: function(field_values) {
return field_values['action'] == 0;
} },
{ name: "x", type: "double", condition: function(field_values) {
return field_values['action'] == 2 || field_values['action'] == 3;
} },
{ name: "z", type: "double", condition: function(field_values) {
return field_values['action'] == 2 || field_values['action'] == 3;
} },
{ name: "old_radius", type: "double", condition: function(field_values) {
return field_values['action'] == 1 || field_values['action'] == 3;
} },
{ name: "new_radius", type: "double", condition: function(field_values) {
return field_values['action'] == 1 || field_values['action'] == 3;
} },
{ name: "speed", type: "varint", condition: function(field_values) {
2014-12-30 13:40:22 -05:00
return field_values['action'] == 1 || field_values['action'] == 3;
} },
{ name: "portalBoundary", type: "varint", condition: function(field_values) {
return field_values['action'] == 3;
} },
{ name: "warning_time", type: "varint", condition: function(field_values) {
return field_values['action'] == 4 || field_values['action'] == 3;
} },
{ name: "warning_blocks", type: "varint", condition: function(field_values) {
return field_values['action'] == 5 || field_values['action'] == 3;
} }
2014-12-30 13:23:58 -05:00
]},
title: { id: 0x45, fields: [
2014-12-30 13:55:38 -05:00
{ name: "action", type: "varint"},
{ name: "text", type: "string", condition: function(field_values) {
return field_values['action'] == 0 || field_values['action'] == 1;
} },
{ name: "fadeIn", type: "int", condition: function(field_values) {
2014-12-31 13:17:02 -05:00
return field_values['action'] == 2;
2014-12-30 13:55:38 -05:00
} },
{ name: "stay", type: "int", condition: function(field_values) {
return field_values['action'] == 2;
} },
{ name: "fadeOut", type: "int", condition: function(field_values) {
return field_values['action'] == 2;
} }
2014-12-30 13:23:58 -05:00
]},
set_compression: { id: 0x46, fields: [
{ name: "threshold", type: "varint"}
]},
playerlist_header: { id: 0x47, fields: [
{ name: "header", type: "string" },
2014-12-30 13:59:57 -05:00
{ name: "footer", type: "string" }
2014-12-30 13:23:58 -05:00
]},
resource_pack_send: { id: 0x48, fields: [
{ name: "url", type: "string" },
{ name: "hash", type: "string" }
]},
update_entity_nbt: { id: 0x49, fields: [
2014-12-30 13:59:57 -05:00
{ name: "entityId", type: "varint" },
2014-12-31 13:17:02 -05:00
{ name: "tag", type: "restBuffer"}
2014-01-14 00:56:56 -05:00
]}
},
2014-01-14 00:56:56 -05:00
toServer: {
keep_alive: {id: 0x00, fields: [
{ name: "keepAliveId", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
chat: {id: 0x01, fields: [
{ name: "message", type: "string" }
2014-01-14 00:56:56 -05:00
]},
use_entity: {id: 0x02, fields: [
{ name: "target", type: "varint" },
2014-12-30 13:23:58 -05:00
{ name: "mouse", type: "byte" },
{ name: "x", type: "float"},
{ name: "y", type: "float"},
{ name: "size", type: "float"}
2014-01-14 00:56:56 -05:00
]},
flying: {id: 0x03, fields: [
{ name: "onGround", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
position: {id: 0x04, fields: [
{ name: "x", type: "double" },
{ name: "stance", type: "double" },
{ name: "y", type: "double" },
{ name: "z", type: "double" },
{ name: "onGround", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
look: {id: 0x05, fields: [
{ name: "yaw", type: "float" },
{ name: "pitch", type: "float" },
{ name: "onGround", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
position_look: {id: 0x06, fields: [
{ name: "x", type: "double" },
{ name: "y", type: "double" },
{ name: "z", type: "double" },
{ name: "yaw", type: "float" },
{ name: "pitch", type: "float" },
{ name: "onGround", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
block_dig: {id: 0x07, fields: [
{ name: "status", type: "byte" },
2014-12-30 13:59:57 -05:00
{ name: "location", type: "position"},
{ name: "face", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
block_place: {id: 0x08, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "direction", type: "byte" },
{ name: "heldItem", type: "slot" },
{ name: "cursorX", type: "byte" },
{ name: "cursorY", type: "byte" },
{ name: "cursorZ", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
held_item_slot: {id: 0x09, fields: [
{ name: "slotId", type: "short" }
2014-01-14 00:56:56 -05:00
]},
arm_animation: {id: 0x0a, fields: [
2014-12-30 13:23:58 -05:00
{ name: "entityId", type: "int" }, /* TODO: wiki.vg says this is empty? */
{ name: "animation", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
entity_action: {id: 0x0b, fields: [
{ name: "entityId", type: "varint" },
2014-12-30 13:23:58 -05:00
{ name: "actionId", type: "varint" },
{ name: "jumpBoost", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
steer_vehicle: {id: 0x0c, fields: [
{ name: "sideways", type: "float" },
{ name: "forward", type: "float" },
2014-12-30 13:23:58 -05:00
{ name: "jump", type: "ubyte" }
2014-01-14 00:56:56 -05:00
]},
close_window: {id: 0x0d, fields: [
{ name: "windowId", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
window_click: {id: 0x0e, fields: [
{ name: "windowId", type: "byte" },
{ name: "slot", type: "short" },
{ name: "mouseButton", type: "byte" },
{ name: "action", type: "short" },
{ name: "mode", type: "byte" },
{ name: "item", type: "slot" }
2014-01-14 00:56:56 -05:00
]},
transaction: {id: 0x0f, fields: [
{ name: "windowId", type: "byte" },
{ name: "action", type: "short" },
{ name: "accepted", type: "bool" }
2014-01-14 00:56:56 -05:00
]},
set_creative_slot: {id: 0x10, fields: [
{ name: "slot", type: "short" },
{ name: "item", type: "slot" }
2014-01-14 00:56:56 -05:00
]},
enchant_item: {id: 0x11, fields: [
{ name: "windowId", type: "byte" },
{ name: "enchantment", type: "byte" }
2014-01-14 00:56:56 -05:00
]},
update_sign: {id: 0x12, fields: [
2014-12-30 13:23:58 -05:00
{ name: "location", type: "position" },
{ name: "text1", type: "string" },
{ name: "text2", type: "string" },
{ name: "text3", type: "string" },
{ name: "text4", type: "string" }
2014-01-14 00:56:56 -05:00
]},
abilities: {id: 0x13, fields: [
{ name: "flags", type: "byte" },
{ name: "flyingSpeed", type: "float" },
{ name: "walkingSpeed", type: "float" }
2014-01-14 00:56:56 -05:00
]},
tab_complete: {id: 0x14, fields: [
2014-12-30 13:23:58 -05:00
{ name: "text", type: "string" },
{ name: "hasPosition", type: "bool" },
2014-12-30 13:23:58 -05:00
{ name: "block", type: "position", condition: function(field_values) {
return field_values['hasPosition'];
} }
2014-01-14 00:56:56 -05:00
]},
settings: {id: 0x15, fields: [
{ name: "locale", type: "string" },
{ name: "viewDistance", type: "byte" },
{ name: "chatFlags", type: "byte" },
{ name: "chatColors", type: "bool" },
2014-12-30 13:23:58 -05:00
{ name: "skinParts", type: "ubyte" }
2014-01-14 00:56:56 -05:00
]},
client_command: {id: 0x16, fields: [
2014-12-30 13:23:58 -05:00
{ name: "payload", type: "varint" }
2014-01-14 00:56:56 -05:00
]},
custom_payload: {id: 0x17, fields: [
2014-12-30 13:23:58 -05:00
{ name: "channel", type: "string" }, /* TODO: wiki.vg sats no dataLength is needed? */
{ name: "data", type: "restBuffer"}
2014-12-30 13:23:58 -05:00
]},
spectate: { id: 0x18, fields: [
{ name: "target", type: "UUID"}
]},
resource_pack_receive: { id: 0x19, fields: [
{ name: "hash", type: "string" },
{ name: "result", type: "varint" }
2014-01-14 00:56:56 -05:00
]}
}
}
2013-01-03 23:14:19 -05:00
};
2014-01-14 00:56:56 -05:00
var packetFields = {};
var packetNames = {};
var packetIds = {};
var packetStates = {toClient: {}, toServer: {}};
(function() {
for (var stateName in states) {
var state = states[stateName];
packetFields[state] = {toClient: [], toServer: []};
packetNames[state] = {toClient: [], toServer: []};
packetIds[state] = {toClient: [], toServer: []};
['toClient', 'toServer'].forEach(function(direction) {
for (var name in packets[state][direction]) {
var info = packets[state][direction][name];
var id = info.id;
var fields = info.fields;
assert(id !== undefined, 'missing id for packet '+name);
assert(fields !== undefined, 'missing fields for packet '+name);
assert(!packetNames[state][direction].hasOwnProperty(id), 'duplicate packet id '+id+' for '+name);
assert(!packetIds[state][direction].hasOwnProperty(name), 'duplicate packet name '+name+' for '+id);
assert(!packetFields[state][direction].hasOwnProperty(id), 'duplicate packet id '+id+' for '+name);
assert(!packetStates[direction].hasOwnProperty(name), 'duplicate packet name '+name+' for '+id+', must be unique across all states');
2014-01-14 00:56:56 -05:00
packetNames[state][direction][id] = name;
packetIds[state][direction][name] = id;
packetFields[state][direction][id] = fields;
packetStates[direction][name] = state;
}
});
}
})();
2013-01-07 22:20:40 -05:00
var types = {
'byte': [readByte, writeByte, 1],
'ubyte': [readUByte, writeUByte, 1],
'short': [readShort, writeShort, 2],
'ushort': [readUShort, writeUShort, 2],
'int': [readInt, writeInt, 4],
'long': [readLong, writeLong, 8],
'varint': [readVarInt, writeVarInt, sizeOfVarInt],
'float': [readFloat, writeFloat, 4],
'double': [readDouble, writeDouble, 8],
'bool': [readBool, writeBool, 1],
'string': [readString, writeString, sizeOfString],
'ustring': [readString, writeString, sizeOfUString], // TODO : remove ustring
'UUID': [readUUID, writeUUID, 16],
'container': [readContainer, writeContainer, sizeOfContainer],
'array': [readArray, writeArray, sizeOfArray],
'buffer': [readBuffer, writeBuffer, sizeOfBuffer],
'restBuffer': [readRestBuffer, writeBuffer, sizeOfBuffer],
'count': [readCount, writeCount, sizeOfCount],
// TODO : remove type-specific, replace with generic containers and arrays.
'position': [readPosition, writePosition, 8],
'slot': [readSlot, writeSlot, sizeOfSlot],
'entityMetadata': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
2013-01-03 23:14:19 -05:00
};
var debug;
if (process.env.NODE_DEBUG && /(minecraft-protocol|mc-proto)/.test(process.env.NODE_DEBUG)) {
var pid = process.pid;
debug = function(x) {
// if console is not set up yet, then skip this.
if (!console.error)
return;
console.error('MC-PROTO: %d', pid,
util.format.apply(util, arguments).slice(0, 500));
};
} else {
debug = function() { };
}
var entityMetadataTypes = {
0: { type: 'byte' },
1: { type: 'short' },
2: { type: 'int' },
3: { type: 'float' },
4: { type: 'string' },
5: { type: 'slot' },
6: { type: 'container', typeArgs: { fields: [
{ name: 'x', type: 'int' },
{ name: 'y', type: 'int' },
{ name: 'z', type: 'int' }
]}}
};
// maps string type name to number
var entityMetadataTypeBytes = {};
for (var n in entityMetadataTypes) {
if (!entityMetadataTypes.hasOwnProperty(n)) continue;
entityMetadataTypeBytes[entityMetadataTypes[n].type] = n;
}
function sizeOfEntityMetadata(value) {
var size = 1 + value.length;
var item;
for (var i = 0; i < value.length; ++i) {
item = value[i];
size += sizeOf(item.value, entityMetadataTypes[entityMetadataTypeBytes[item.type]], {});
}
return size;
2013-01-08 00:50:43 -05:00
}
function writeEntityMetadata(value, buffer, offset) {
value.forEach(function(item) {
var type = entityMetadataTypeBytes[item.type];
var headerByte = (type << 5) | item.key;
buffer.writeUInt8(headerByte, offset);
offset += 1;
offset = write(item.value, buffer, offset, entityMetadataTypes[type], {});
});
buffer.writeUInt8(0x7f, offset);
return offset + 1;
}
2013-01-08 00:50:43 -05:00
function writeUUID(value, buffer, offset) {
buffer.writeUInt32BE(value[0], offset);
buffer.writeUInt32BE(value[1], offset + 4);
buffer.writeUInt32BE(value[2], offset + 8);
buffer.writeUInt32BE(value[3], offset + 12);
return offset + 16;
}
2013-01-03 23:14:19 -05:00
function readEntityMetadata(buffer, offset) {
var cursor = offset;
var metadata = [];
var item, key, type, results, reader, typeName, dataType;
2013-01-03 23:14:19 -05:00
while (true) {
if (cursor + 1 > buffer.length) return null;
item = buffer.readUInt8(cursor);
cursor += 1;
if (item === 0x7f) {
return {
value: metadata,
size: cursor - offset,
};
}
2013-01-03 23:14:19 -05:00
key = item & 0x1f;
type = item >> 5;
dataType = entityMetadataTypes[type];
typeName = dataType.type;
debug("Reading entity metadata type " + dataType + " (" + ( typeName || "unknown" ) + ")");
if (!dataType) {
return {
error: new Error("unrecognized entity metadata type " + type)
}
}
results = read(buffer, cursor, dataType, {});
2013-01-03 23:14:19 -05:00
if (! results) return null;
metadata.push({
key: key,
value: results.value,
type: typeName,
});
2013-01-03 23:14:19 -05:00
cursor += results.size;
}
}
function readString (buffer, offset) {
var length = readVarInt(buffer, offset);
if (!!!length) return null;
var cursor = offset + length.size;
var stringLength = length.value;
var strEnd = cursor + stringLength;
2013-01-03 23:14:19 -05:00
if (strEnd > buffer.length) return null;
2014-12-30 13:23:58 -05:00
var value = buffer.toString('utf8', cursor, strEnd);
cursor = strEnd;
2014-12-30 13:23:58 -05:00
2013-01-03 23:14:19 -05:00
return {
value: value,
size: cursor - offset,
2013-01-03 23:14:19 -05:00
};
}
function readUUID(buffer, offset) {
return {
value: [
buffer.readUInt32BE(offset),
buffer.readUInt32BE(offset + 4),
buffer.readUInt32BE(offset + 8),
buffer.readUInt32BE(offset + 12),
],
size: 16,
};
}
2013-01-03 23:14:19 -05:00
function readShort(buffer, offset) {
if (offset + 2 > buffer.length) return null;
var value = buffer.readInt16BE(offset);
return {
value: value,
size: 2,
};
}
function readUShort(buffer, offset) {
if (offset + 2 > buffer.length) return null;
var value = buffer.readUInt16BE(offset);
return {
value: value,
size: 2,
};
}
function readInt(buffer, offset) {
if (offset + 4 > buffer.length) return null;
var value = buffer.readInt32BE(offset);
return {
value: value,
size: 4,
};
}
function readFloat(buffer, offset) {
if (offset + 4 > buffer.length) return null;
var value = buffer.readFloatBE(offset);
return {
value: value,
size: 4,
};
}
function readDouble(buffer, offset) {
if (offset + 8 > buffer.length) return null;
var value = buffer.readDoubleBE(offset);
return {
value: value,
size: 8,
};
}
function readLong(buffer, offset) {
if (offset + 8 > buffer.length) return null;
return {
value: [buffer.readInt32BE(offset), buffer.readInt32BE(offset + 4)],
size: 8,
};
}
function readByte(buffer, offset) {
if (offset + 1 > buffer.length) return null;
var value = buffer.readInt8(offset);
return {
value: value,
size: 1,
};
}
function readUByte(buffer, offset) {
if (offset + 1 > buffer.length) return null;
var value = buffer.readUInt8(offset);
return {
value: value,
size: 1,
};
}
function readBool(buffer, offset) {
if (offset + 1 > buffer.length) return null;
var value = buffer.readInt8(offset);
return {
value: !!value,
size: 1,
};
}
function readPosition(buffer, offset) {
var longVal = readLong(buffer, offset).value; // I wish I could do destructuring...
var x = longVal[0] >> 6;
var y = ((longVal[0] & 0x3F) << 6) | (longVal[1] >> 26);
var z = longVal[1] << 6 >> 6
return {
value: { x: x, y: y, z: z },
size: 8
};
}
2013-01-03 23:14:19 -05:00
function readSlot(buffer, offset) {
var results = readShort(buffer, offset);
if (! results) return null;
var blockId = results.value;
var cursor = offset + results.size;
if (blockId === -1) {
return {
value: { id: blockId },
size: cursor - offset,
};
}
var cursorEnd = cursor + 5;
if (cursorEnd > buffer.length) return null;
var itemCount = buffer.readInt8(cursor);
var itemDamage = buffer.readInt16BE(cursor + 1);
var nbtDataSize = buffer.readInt16BE(cursor + 3);
if (nbtDataSize === -1) nbtDataSize = 0;
var nbtDataEnd = cursorEnd + nbtDataSize;
if (nbtDataEnd > buffer.length) return null;
2013-01-03 23:14:19 -05:00
var nbtData = buffer.slice(cursorEnd, nbtDataEnd);
return {
value: {
id: blockId,
itemCount: itemCount,
itemDamage: itemDamage,
nbtData: nbtData,
},
size: nbtDataEnd - offset,
};
}
function sizeOfSlot(value) {
return value.id === -1 ? 2 : 7 + value.nbtData.length;
2013-01-03 23:14:19 -05:00
}
function writePosition(value, buffer, offset) {
var longVal = [];
longVal[0] = ((value.x & 0x3FFFFFF) << 6) | ((value.y & 0xFC0) >> 6);
longVal[1] = ((value.y & 0x3F) << 26) | (value.z & 0x3FFFFFF);
return writeLong(longVal, buffer, offset);
}
function writeSlot(value, buffer, offset) {
buffer.writeInt16BE(value.id, offset);
if (value.id === -1) return offset + 2;
buffer.writeInt8(value.itemCount, offset + 2);
buffer.writeInt16BE(value.itemDamage, offset + 3);
var nbtDataSize = value.nbtData.length;
2013-01-03 23:14:19 -05:00
if (nbtDataSize === 0) nbtDataSize = -1; // I don't know wtf mojang smokes
buffer.writeInt16BE(nbtDataSize, offset + 5);
value.nbtData.copy(buffer, offset + 7);
return offset + 7 + value.nbtData.length;
}
2013-01-03 23:14:19 -05:00
function sizeOfString(value) {
var length = Buffer.byteLength(value, 'utf8');
assert.ok(length < STRING_MAX_LENGTH, "string greater than max length");
return sizeOfVarInt(length) + length;
2013-01-03 23:14:19 -05:00
}
function sizeOfUString(value) {
var length = Buffer.byteLength(value, 'utf8');
assert.ok(length < SRV_STRING_MAX_LENGTH, "string greater than max length");
return sizeOfVarInt(length) + length;
}
function writeString(value, buffer, offset) {
var length = Buffer.byteLength(value, 'utf8');
offset = writeVarInt(length, buffer, offset);
buffer.write(value, offset, length, 'utf8');
return offset + length;
}
2013-01-03 23:14:19 -05:00
function writeByte(value, buffer, offset) {
buffer.writeInt8(value, offset);
return offset + 1;
2013-01-03 23:14:19 -05:00
}
function writeBool(value, buffer, offset) {
buffer.writeInt8(+value, offset);
return offset + 1;
2013-01-03 23:14:19 -05:00
}
function writeUByte(value, buffer, offset) {
buffer.writeUInt8(value, offset);
return offset + 1;
}
2013-01-03 23:14:19 -05:00
function writeFloat(value, buffer, offset) {
buffer.writeFloatBE(value, offset);
return offset + 4;
2013-01-03 23:14:19 -05:00
}
function writeDouble(value, buffer, offset) {
buffer.writeDoubleBE(value, offset);
return offset + 8;
2013-01-03 23:14:19 -05:00
}
function writeShort(value, buffer, offset) {
buffer.writeInt16BE(value, offset);
return offset + 2;
2013-01-07 22:14:14 -05:00
}
function writeUShort(value, buffer, offset) {
buffer.writeUInt16BE(value, offset);
return offset + 2;
2013-01-07 22:14:14 -05:00
}
function writeInt(value, buffer, offset) {
buffer.writeInt32BE(value, offset);
return offset + 4;
2013-01-03 23:14:19 -05:00
}
function writeLong(value, buffer, offset) {
buffer.writeInt32BE(value[0], offset);
buffer.writeInt32BE(value[1], offset + 4);
return offset + 8;
2013-01-07 23:36:14 -05:00
}
function readVarInt(buffer, offset) {
var result = 0;
var shift = 0;
var cursor = offset;
2014-12-30 13:23:58 -05:00
while (true) {
if (cursor + 1 > buffer.length) return null;
var b = buffer.readUInt8(cursor);
result |= ((b & 0x7f) << shift); // Add the bits to our number, except MSB
cursor++;
if (!(b & 0x80)) { // If the MSB is not set, we return the number
return {
value: result,
size: cursor - offset
};
}
shift += 7; // we only have 7 bits, MSB being the return-trigger
assert.ok(shift < 64, "varint is too big"); // Make sure our shift don't overflow.
}
}
function sizeOfVarInt(value) {
var cursor = 0;
while (value & ~0x7F) {
value >>>= 7;
cursor++;
}
return cursor + 1;
}
function writeVarInt(value, buffer, offset) {
var cursor = 0;
while (value & ~0x7F) {
buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor);
cursor++;
value >>>= 7;
}
buffer.writeUInt8(value, offset + cursor);
return offset + cursor + 1;
}
function readContainer(buffer, offset, typeArgs, rootNode) {
var results = {
value: {},
size: 0
};
// BLEIGH. Huge hack because I have no way of knowing my current name.
// TODO : either pass fieldInfo instead of typeArgs as argument (bleigh), or send name as argument (verybleigh).
rootNode.this = results.value;
for (var index in typeArgs.fields) {
var readResults = read(buffer, offset, typeArgs.fields[index], rootNode);
if (readResults == null) { continue; }
results.size += readResults.size;
offset += readResults.size;
results.value[typeArgs.fields[index].name] = readResults.value;
}
delete rootNode.this;
return results;
}
function writeContainer(value, buffer, offset, typeArgs, rootNode) {
rootNode.this = value;
for (var index in typeArgs.fields) {
if (!value.hasOwnProperty(typeArgs.fields[index].name && typeArgs.fields[index].type != "count" && !typeArgs.fields[index].condition))
debug(new Error("Missing Property " + typeArgs.fields[index].name).stack);
offset = write(value[typeArgs.fields[index].name], buffer, offset, typeArgs.fields[index], rootNode);
}
delete rootNode.this;
return offset;
}
function sizeOfContainer(value, typeArgs, rootNode) {
var size = 0;
rootNode.this = value;
for (var index in typeArgs.fields) {
size += sizeOf(value[typeArgs.fields[index].name], typeArgs.fields[index], rootNode);
}
delete rootNode.this;
return size;
}
2014-12-30 13:23:58 -05:00
function readBuffer(buffer, offset, typeArgs, rootNode) {
var count = getField(typeArgs.count, rootNode);
return {
value: buffer.slice(offset, offset + count),
size: count
};
}
function writeBuffer(value, buffer, offset) {
value.copy(buffer, offset);
return offset + value.length;
}
function sizeOfBuffer(value) {
return value.length;
}
2014-12-31 13:17:02 -05:00
function readRestBuffer(buffer, offset, typeArgs, rootNode) {
return {
value: buffer.slice(offset),
size: buffer.length - offset
};
}
function readArray(buffer, offset, typeArgs, rootNode) {
var results = {
value: [],
size: 0
}
var count = getField(typeArgs.count, rootNode);
for (var i = 0; i < count; i++) {
var readResults = read(buffer, offset, { type: typeArgs.type, typeArgs: typeArgs.typeArgs }, rootNode);
results.size += readResults.size;
offset += readResults.size;
results.value.push(readResults.value);
}
return results;
}
function writeArray(value, buffer, offset, typeArgs, rootNode) {
for (var index in value) {
offset = write(value[index], buffer, offset, { type: typeArgs.type, typeArgs: typeArgs.typeArgs }, rootNode);
}
return offset;
}
function sizeOfArray(value, typeArgs, rootNode) {
var size = 0;
for (var index in value) {
size += sizeOf(value[index], { type: typeArgs.type, typeArgs: typeArgs.typeArgs }, rootNode);
}
return size;
}
function getField(countField, rootNode) {
var countFieldArr = countField.split(".");
var count = rootNode;
for (var index = 0; index < countFieldArr.length; index++) {
count = count[countFieldArr[index]];
}
return count;
}
function readCount(buffer, offset, typeArgs, rootNode) {
return read(buffer, offset, { type: 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 write(getField(typeArgs.countFor, rootNode).length, buffer, offset, { type: typeArgs.type }, rootNode);
}
function sizeOfCount(value, typeArgs, rootNode) {
// TODO : should I use value or getField().length ?
/*console.log(rootNode);
console.log(typeArgs);*/
return sizeOf(getField(typeArgs.countFor, rootNode).length, { type: typeArgs.type }, rootNode);
}
function read(buffer, cursor, fieldInfo, rootNodes) {
if (fieldInfo.condition && !fieldInfo.condition(rootNodes)) {
return null;
}
var type = types[fieldInfo.type];
if (!type) {
return {
error: new Error("missing data type: " + fieldInfo.type)
};
}
var readResults = type[0](buffer, cursor, fieldInfo.typeArgs, rootNodes);
if (readResults.error) return { error: readResults.error };
return readResults;
}
function write(value, buffer, offset, fieldInfo, rootNode) {
if (fieldInfo.condition && !fieldInfo.condition(rootNode)) {
2014-05-14 14:46:29 -04:00
return offset;
}
var type = types[fieldInfo.type];
if (!type) {
return {
error: new Error("missing data type: " + fieldInfo.type)
};
}
return type[1](value, buffer, offset, fieldInfo.typeArgs, rootNode);
}
function sizeOf(value, fieldInfo, rootNode) {
if (fieldInfo.condition && !fieldInfo.condition(rootNode)) {
return 0;
}
var type = types[fieldInfo.type];
if (!type) {
throw new Error("missing data type: " + fieldInfo.type);
}
if (typeof type[2] === 'function') {
return type[2](value, fieldInfo.typeArgs, rootNode);
} else {
return type[2];
}
}
function get(packetId, state, toServer) {
var direction = toServer ? "toServer" : "toClient";
2014-01-14 00:56:56 -05:00
var packetInfo = packetFields[state][direction][packetId];
2013-01-28 19:44:00 -05:00
if (!packetInfo) {
return null;
}
return packetInfo;
2013-01-04 01:45:57 -05:00
}
// TODO : This does NOT contain the length prefix anymore.
function createPacketBuffer(packetId, state, params, isServer) {
var length = 0;
2014-01-14 00:56:56 -05:00
if (typeof packetId === 'string' && typeof state !== 'string' && !params) {
// simplified two-argument usage, createPacketBuffer(name, params)
params = state;
state = packetStates[!isServer ? 'toServer' : 'toClient'][packetId];
}
if (typeof packetId === 'string') packetId = packetIds[state][!isServer ? 'toServer' : 'toClient'][packetId];
assert.notEqual(packetId, undefined);
var packet = get(packetId, state, !isServer);
2013-01-28 19:44:00 -05:00
assert.notEqual(packet, null);
2013-01-03 23:14:19 -05:00
packet.forEach(function(fieldInfo) {
length += sizeOf(params[fieldInfo.name], fieldInfo, params);
2013-01-03 23:14:19 -05:00
});
length += sizeOfVarInt(packetId);
var size = length;// + sizeOfVarInt(length);
2013-01-03 23:14:19 -05:00
var buffer = new Buffer(size);
var offset = 0;//writeVarInt(length, buffer, 0);
offset = writeVarInt(packetId, buffer, offset);
packet.forEach(function(fieldInfo) {
var value = params[fieldInfo.name];
// TODO : A better check is probably needed
if(typeof value === "undefined" && fieldInfo.type != "count" && !fieldInfo.condition)
debug(new Error("Missing Property " + fieldInfo.name).stack);
offset = write(value, buffer, offset, fieldInfo, params);
2013-01-03 23:14:19 -05:00
});
return buffer;
}
function compressPacketBuffer(buffer, callback) {
var dataLength = buffer.size;
2015-02-21 19:41:41 -05:00
zlib.deflate(buffer, function(err, buf) {
if (err)
callback(err);
else
newStylePacket(buffer, callback);
});
}
2015-02-21 19:41:41 -05:00
function oldStylePacket(buffer, callback) {
var packet = new Buffer(sizeOfVarInt(buffer.length) + buffer.length);
var cursor = writeVarInt(buffer.length, packet, 0);
writeBuffer(buffer, packet, cursor);
2015-02-21 19:41:41 -05:00
callback(null, packet);
}
2015-02-21 19:41:41 -05:00
function newStylePacket(buffer, callback) {
var sizeOfDataLength = sizeOfVarInt(0);
var sizeOfLength = sizeOfVarInt(buffer.length + sizeOfDataLength);
var size = sizeOfLength + sizeOfDataLength + buffer.length;
var packet = new Buffer(size);
2015-02-21 19:41:41 -05:00
var cursor = writeVarInt(size - sizeOfLength, packet, 0);
cursor = writeVarInt(0, packet, cursor);
writeBuffer(buffer, packet, cursor);
2015-02-21 19:41:41 -05:00
callback(null, packet);
}
2015-02-21 16:04:41 -05:00
function parsePacketData(buffer, state, isServer, packetsToParse) {
var cursor = 0;
2014-04-10 19:48:06 -04:00
var packetIdField = readVarInt(buffer, cursor);
var packetId = packetIdField.value;
cursor += packetIdField.size;
2014-12-30 13:23:58 -05:00
var results = { id: packetId, state: state };
2014-04-10 19:48:06 -04:00
// Only parse the packet if there is a need for it, AKA if there is a listener attached to it
var name = packetNames[state][isServer ? "toServer" : "toClient"][packetId];
2014-04-10 20:08:17 -04:00
var shouldParse = (!packetsToParse.hasOwnProperty(name) || packetsToParse[name] <= 0)
&& (!packetsToParse.hasOwnProperty("packet") || packetsToParse["packet"] <= 0);
if (shouldParse) {
2014-04-10 19:48:06 -04:00
return {
buffer: buffer,
results: results
2014-04-10 19:48:06 -04:00
};
}
2014-12-30 13:23:58 -05:00
var packetInfo = get(packetId, state, isServer);
if (packetInfo === null) {
2013-01-28 19:44:00 -05:00
return {
error: new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")"),
2014-04-10 19:48:06 -04:00
buffer: buffer,
results: results
2014-04-10 19:48:06 -04:00
};
} else {
var packetName = packetNames[state][isServer ? "toServer" : "toClient"][packetId];
debug("read packetId " + state + "." + packetName + " (0x" + packetId.toString(16) + ")");
2013-01-28 19:44:00 -05:00
}
2014-12-30 13:23:58 -05:00
var i, fieldInfo, readResults;
2013-01-03 23:14:19 -05:00
for (i = 0; i < packetInfo.length; ++i) {
fieldInfo = packetInfo[i];
readResults = read(buffer, cursor, fieldInfo, results);
/* A deserializer cannot return null anymore. Besides, read() returns
* null when the condition is not fulfilled.
if (!!!readResults) {
var error = new Error("A deserializer returned null");
error.packetId = packetId;
error.fieldInfo = fieldInfo.name;
return {
size: length + lengthField.size,
error: error,
results: results
};
}*/
if (readResults === null) continue;
if (readResults.error) {
return readResults;
2013-01-28 19:44:00 -05:00
}
2013-02-10 21:05:42 -05:00
results[fieldInfo.name] = readResults.value;
cursor += readResults.size;
2013-01-03 23:14:19 -05:00
}
debug(results);
2013-01-03 23:14:19 -05:00
return {
results: results,
2014-04-10 19:48:06 -04:00
buffer: buffer
2013-01-03 23:14:19 -05:00
};
}
2013-01-07 23:36:14 -05:00
2015-02-21 16:04:41 -05:00
function parsePacket(buffer, state, isServer, packetsToParse) {
if (state == null) state = states.PLAY;
var cursor = 0;
var lengthField = readVarInt(buffer, 0);
if (!!!lengthField) return null;
var length = lengthField.value;
cursor += lengthField.size;
2015-02-21 16:04:41 -05:00
if (length + lengthField.size > buffer.length) return null; // fail early
var result = parsePacketData(buffer.slice(cursor, length + cursor), state, isServer, packetsToParse);
result.size = lengthField.size + length;
return result;
}
2015-02-21 16:04:41 -05:00
function parseNewStylePacket(buffer, state, isServer, packetsToParse, cb) {
2015-02-21 19:41:41 -05:00
var dataLengthField = readVarInt(buffer, 0);
var buf = buffer.slice(dataLengthField.size);
if(dataLengthField.value != 0) {
2015-02-21 19:41:41 -05:00
zlib.inflate(buf, function(err, newbuf) {
if (err) {
console.log(err);
cb(err);
} else {
2015-02-21 16:04:41 -05:00
cb(null, parsePacketData(newbuf, state, isServer, packetsToParse));
}
2015-01-02 12:50:54 -05:00
});
} else {
2015-02-21 19:41:41 -05:00
cb(null, parsePacketData(buf, state, isServer, packetsToParse));
2015-01-02 12:50:54 -05:00
}
}
2013-01-07 23:36:14 -05:00
module.exports = {
2014-12-30 13:24:39 -05:00
version: 47,
minecraftVersion: '1.8.1',
2013-01-07 23:36:14 -05:00
sessionVersion: 13,
parsePacket: parsePacket,
2015-02-21 19:41:41 -05:00
parsePacketData: parsePacketData,
2015-01-02 12:50:54 -05:00
parseNewStylePacket: parseNewStylePacket,
2013-01-07 23:36:14 -05:00
createPacketBuffer: createPacketBuffer,
compressPacketBuffer: compressPacketBuffer,
oldStylePacket: oldStylePacket,
newStylePacket: newStylePacket,
2013-01-07 23:36:14 -05:00
STRING_MAX_LENGTH: STRING_MAX_LENGTH,
2014-01-14 00:56:56 -05:00
packetIds: packetIds,
packetNames: packetNames,
packetFields: packetFields,
2014-04-10 19:48:06 -04:00
packetStates: packetStates,
2015-02-21 19:41:41 -05:00
types: types,
states: states,
2013-01-07 23:36:14 -05:00
get: get,
debug: debug,
2013-01-07 23:36:14 -05:00
};